I just wondering if there is a good practice about LaunchScreen. The reason I'm asking this is, if one adds LaunchScreen, there is a white flash before react-native kicks in and load the app. Is there any way we can prevent this?
@alinz - this is fixed in the next release: https://github.com/tadeuzagallo/react-native/commit/8d992262ed2dcf0d46e8d22e605764902c33012f
@alinz does it look something like https://www.dropbox.com/s/xq522ywqsd16vqe/example.mov?dl=0
@liubko Yes, this is exactly what I want to get rid of. I haven't applied the patch yet but I will do it tonight.
@brentvatne: I don't see how that patch would fix this problem (if I'm blind/stupid please forgive :-). The problem is that there's a moment between when the app has launched and React is still working. Ideally the LaunchScreen would show until React is finished and fully rendered as it does in a "fully native" app.
@oblador - ah, this would allow you to transition more smoothly by having the same background colour as the launch screen in the "flicker" moment.
@vjeux - how do you get past this issue in FB apps? I noticed that F8, which if I understand correctly is 100% React Native, transitions directly from the launch screen to the app. cc @nicklockwood
There's a (separate) fix for this issue too. Use the new loadingView
property of RCTRootView
, and set it to a full-screen UIImageView
showing your launch image. The code might look something like this:
UIImageView *launchView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"MyLaunchImage"];
rootView.loadingView = launchView;
I've been experimenting with a way to automatically detect the launch image and make this completely automatic, but it's not really ready yet. If you're interested, it looks like this:
// TODO: support landscape orientation
// Get launch image
NSString *launchImageName = nil;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
if (height == 480) launchImageName = @"[email protected]"; // iPhone 4/4s
else if (height == 568) launchImageName = @"[email protected]"; // iPhone 5/5s
else if (height == 667) launchImageName = @"[email protected]"; // iPhone 6
else if (height == 736) launchImageName = @"[email protected]"; // iPhone 6+
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
CGFloat scale = RCTScreenScale();
if (scale == 1) launchImageName = @"LaunchImage-700-Portrait~ipad.png"; // iPad
else if (scale == 2) launchImageName = @"LaunchImage-700-Portrait@2x~ipad.png"; // Retina iPad
}
// Create loading view
UIImage *image = [UIImage imageNamed:launchImageName];
if (image) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect){CGPointZero, RCTScreenSize()}];
imageView.contentMode = UIViewContentModeBottom;
imageView.image = image;
rootView.loadingView = imageView;
}
An easy solution to what @nicklockwood described is to load the launchscreen xib if you're on iOS 8+.
@ide I am using launchscreen xib. I see my lauchscreen view at the launch time but before my app boots up, I see a white view (flash).
@alinz, I believe @ide means to use your launchscreen xib to set the RCTRootView loadingView (which you'll still need to do manually). There is no automatic support for doing that (yet).
To clarify, iOS hides your launch screen at the point when React _begins_ loading. To avoid seeing a blank screen, you'll need to extend the time that the launch screen is displayed by manually showing the same view as the RCTRootView's loadingView.
Thanks @nicklockwood for clarification. That was my bad. :)
Curtesy of http://stackoverflow.com/a/29115477/255765 I ended up with this:
for (NSString *imgName in allPngImageNames){
// Find launch images
if ([imgName containsString:@"LaunchImage"]){
UIImage *img = [UIImage imageNamed:imgName]; //-- this is a launch image
// Has image same scale and dimensions as our current device's screen?
if (img.scale == [UIScreen mainScreen].scale && CGSizeEqualToSize(img.size, [UIScreen mainScreen].bounds.size)) {
NSLog(@"Found launch image for current device %@", img.description);
UIImageView *launchView = [[UIImageView alloc] initWithImage: img];
rootView.loadingView = launchView;
}
}
}
Seems to work.
+1 for a standard/documented way.
@dvine-multimedia does that work better than the code snippet I provided above?
@myusuf3
@nicklockwood Actually they do very much the same thing. When I started to look into this I probably didn't understand what to do with your snipped for pure lack of any xcode experience. When I finally got to the point where I understood what to do, I didn't realize, that you already had been there. Beside that, I personally find "my" solution a bit more readable. But that pure aesthetics.
@nicklockwood I tried your code but i still see a white flash. I created a launch image to set as the loading view. launch image is same as the launchScreen.xib.
This is the only thing holding the release of our app. Really appreciate any help on this.
Is the flash appearing after the launch image is hidden, or does the launch image fail to appear? (This may be hard to determine just by looking - change the launch image to something like a solid red rectangle temporarily to be sure).
@nicklockwood I tried to change the color between xib file and the launchScreen Image. I was still seeing that flash.
My code looks like this:
UIImageView *launchView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"launchScreen1"]];
rootView.loadingView = launchView;
rootView.loadingViewFadeDelay = 0.0;
rootView.loadingViewFadeDuration = 0.15;
I removed the last two line loadingViewFadeDelay and duration now and it seems the problem is solved. I don't see any flash. Nice smooth transition.
@chirag04 I just tried it with sample project and I didn't see the flash :(
here's what I did
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"whiteFlash"
launchOptions:launchOptions];
//here's what I did
UIImage *image = [UIImage imageNamed:@"LaunchImage"];
if (image) {
UIImageView *launchView = [[UIImageView alloc] initWithImage: image];
launchView.contentMode = UIViewContentModeBottom;
launchView.image = image;
rootView.loadingView = launchView;
}
///////////////////////////
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [[UIViewController alloc] init];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
Ok. digging deeper it seems the flash maybe coz my app renders nothing until a value is loaded from asyncstore.
render() {
if(!this.state.loaded) return (null);
return <View ...../>;
}
I think the loadingView thinks that the js finished rendering and hides the loadingView. However js just finished rendering with that return null
@nicklockwood @alinz I'm just shooting in the air. may not be the actual reason. What do you guys think.
Also one thing i noticed is that if i set those loadingViewFadeDelay etc values to 0.30 or higher, the transition is very very smooth. the defaults are 0.25 which is also smooth.
The purpose of the loadingFadeDelay is precisely to cover the scenario where your app doesn't render anything up immediately, so you're using it correctly.
You might want to consider setting the loadingViewFadeDelay explicitly though, in case the default ever changes - and make sure you test on the slowest device you support, in case it takes longer to load.
I like @nicklockwood solution but you have to consider network latency which can not be predict.
One other solution is, set component state to some sort of default value to tell your component to display some sort of loading dialog.
Cool. I think i have the solution.
What do you guys think about having 3 components all being the same.
1) launchScreen.xib
2) loadingView
3) return null in render to actually render a view which is like loadingView and launchScreen.xib?
Not sure how point 3 will scale on different devices though. Would you guys even suggest that?
If the startup time depends on network then tes, I'd definitely advocate drawing an "empty" view immediately instead of hoping it downloads faster than fade delay.
depends on asyncstorage. no network
Whether you are depending on network or not, I don't really like using time base display. There is no way you can 100% guarantee that the time you set is accurate. You should always display something to user to indicate that something is happening but it is not ready to display. This is just a suggestion.
Agreed. The purpose of the loadingView, like the standard iOS loading image, is just to to cover until your app is ready to display something, even if all the app is ready to display is a loading spinner.
That said, the reason I made it a view instead of an image is so you can make it non-static. You could add a UIActivityIndicatorView to the loadingView if you want.
How can the launch image be positioned? I've tried adjusting the frame origin, but it always positions it in the center no matter what origin I used. I have a small top bar image that I want to position top and stretched from left to right.
This is what I have:
UIImageView *launchView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"LaunchImage"]];
CGRect frame = launchView.frame;
frame.origin.x = 0;
frame.origin.y = 0;
launchView.frame = frame;
rootView.loadingView = launchView;
rootView.loadingViewFadeDelay = 0.0;
rootView.loadingViewFadeDuration = 0.0;
It's not a launch image, it's a launch _view_, so you can position your imageView inside another container view and use the container as the launch view:
UIView *launchView = [[UIView alloc] init];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"LaunchImage"]];
imageView.frame = CGRectMake(whatever);
[launchView addSubview:imageView];
...
True, I suppose I could make the wrapper fill the screen space and position the image at the top. The wrapper will be automatically positioned in the center by the RootView it seems https://github.com/facebook/react-native/blob/757d6d204ae2d743634af64e3f78a4aad9d53f70/React/Base/RCTRootView.m#L191.
@Intellicode yes.
Just broke my app in production on iphone 4s using @dvine-multimedia's code. fix is to just put a break in the if.
One more crash because containsString
is not available on ios 7.
@nicklockwood wondering if loading view will automatically pick the best resolution possible?
loadingView doesn't choose an image at all, it just uses what you pass in. The snippet I posted above for finding the launch inage will select the correct resolution for the device though.
Your snippet is not picking the one with right dimensions though. any fix for that?
Were you using my solution or dvine's? What iPhone model are you using? Portrait or landscape?
when using dvine's it picks up the right dimension. With ur solution it is having black space around the image. I assume the black space is coz it picks up the the image with wrong dimension.
I'm on iphone 6. portrait.
This is how it looks. Notice the white background.
Using the following code:
UIImageView *launchView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"LaunchImage"]];
rootView.loadingView = launchView;
rootView.loadingViewFadeDelay = 0.20;
rootView.loadingViewFadeDuration = 0.20;
That wasn't the solution I posted, this was:
// Get launch image
NSString *launchImageName = nil;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
if (height == 480) launchImageName = @"[email protected]"; // iPhone 4/4s
else if (height == 568) launchImageName = @"[email protected]"; // iPhone 5/5s
else if (height == 667) launchImageName = @"[email protected]"; // iPhone 6
else if (height == 736) launchImageName = @"[email protected]"; // iPhone 6+
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
CGFloat scale = RCTScreenScale();
if (scale == 1) launchImageName = @"LaunchImage-700-Portrait~ipad.png"; // iPad
else if (scale == 2) launchImageName = @"LaunchImage-700-Portrait@2x~ipad.png"; // Retina iPad
}
// Create loading view
UIImage *image = [UIImage imageNamed:launchImageName];
if (image) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect){CGPointZero, RCTScreenSize()}];
imageView.contentMode = UIViewContentModeBottom;
imageView.image = image;
rootView.loadingView = imageView;
}
Oh damn! I wish i read your comment carefully. Your solution works and it is easy to understand also.
I've been experimenting with a way to automatically detect the launch image and make this completely automatic, but it's not really ready yet. If you're interested, it looks like this:
Maybe its the experimenting
thing. Not sure how i missed it. Sorry my bad.
Thanks for the help here. Will def use this code in the next version. Seems to cover all the cases as well. Don't need landscape.
Heh, I guess I undersold it. I should've probably just said "this is what we're using in production right now, and it seems to work OK" :-)
"this is what we're using in production right now" would have saved us from some 1 star's on app store. haha. No worries. There is always a new version to save the world. :)
Close this issue btw?
I will go and close this ticket since it's been fully discussed in details we do have some good solutions.
Just to make this 100% clear for those who are using a LaunchScreen xib, here is the code that will make that work:
UIView* launchScreenView = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0];
launchScreenView.frame = self.window.bounds;
rootView.loadingView = launchScreenView;
Add those lines to the application didFinishLaunchingWithOptions
method in the AppDelegate.m
file, just above the line that says return YES
.
@arnemart to be more clear that method will only work for ios 8+ right?
@arnemart Thanks! Works perfectly.
For those who use a LaunchImage
, you need to set both frame
and contentMode
for the image to scale correctly:
UIImageView *launchScreenView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"LaunchImage"]];
launchScreenView.frame = self.window.bounds;
launchScreenView.contentMode = UIViewContentModeScaleAspectFill;
rootView.loadingView = launchScreenView;
Just like the comment from @arnemart, add these lines to the application didFinishLaunchingWithOptions
method in the AppDelegate.m
file, above the line that says return YES
.
Weird. On RN v.0.11.4, the app first launches with a black screen and then LaunchScreen.xib before loading the app. iOS 9 w/ @arnemart's fix https://github.com/facebook/react-native/issues/1402#issuecomment-130236507. Any ideas?
@irfaan try changing window.backgroundColor to something like [UIColor redColor], that should tell us if the black color is coming from the window or something else.
self.window.backgroundColor = [UIColor redColor];
inside didFinishLaunchingWithOptions
changed nothing. Still black before the LaunchScreen.xib. So not from the window, it seems @nicklockwood
How do you recommend I go about debugging this?
It sounds like iOS is not loading your launchScreen.xib at all until the app has loaded (at which point, React is displaying it as the loadingView).
Maybe it's a configuration error in Xcode? Or possibly a bug in iOS 9?
Have you tried on a device, or only the simulator?
Thanks @nicklockwood - I had been experiencing the issue on device. Ran it in the simulator and it worked perfectly, and then deleted + ran on device and it works fine. Really odd. Will post again if the issue crops up again.
hi @genexliu , I added my "LaunchImage" set of images to Images.xcassets/LaunchImage.launchimage/
yet neither the code you pasted nor my LaunchScreen.storyboard can find/use them (both fail silently). My LaunchScreen storyboard is loading correctly as I see its background color but it does not display the referenced image. Any ideas?
Also the startup sequence when running from XCode looks normal but there seems to be an additional run of the start sequence/extra white flash/etc when running natal launch
from cmd line.
Turns out my issue with the Storyboard not finding the LaunchImage was apparently a bug with XCode. I changed the launch setting to 'use asset catalog' and then back to 'use storyboard' and it started working. It's not clear if @genexliu 's code is working for me or not as loadingViewFadeDelay
doesn't seem to be having any effect.
Here's my code:
UIImageView *launchScreenView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Images/LaunchImage"]];
launchScreenView.frame = self.window.bounds;
launchScreenView.contentMode = UIViewContentModeScaleAspectFill;
rootView.loadingView = launchScreenView;
rootView.loadingViewFadeDelay = 5000;
yet there's no delay between the storyboard LaunchImage display and my initial React render
.
@"Images/LaunchImage" doesn't look right to me. It's probably just @"LaunchImage"
Oh, oops, yea. You're right. That was a mis-paste-- one of the permutations I tried (but not the first).
Also, check the example code in my comment above from 3 months ago - there are a lot of launch image files for different device classes.
Doh! You were right. After switching back to LaunchImage
it is now working. Thx!
Can anybody point me to any documentation on this for Android?
+1 for Android
cc @dmmiller do we have a loadingView feature on Android?
Hi, I am running into this same problem, and although I see there are some posted solutions here, I don't understand where to put this code. From my understanding, the solution is to change the Objective-C code, not something that can be done within React Native itself, correct? In that case, where does the posted code go? In the main.m file in the xcode project root? Thanks.
@CorpusCallosum the code should go in the AppDelegate.m file, where the RCTRootView is set up.
Thanks @nicklockwood , I'll give that a try.
Hey this code is working for me now, in so far as I don't see the white flash, and my image is being loaded in to that initial screen, however, now my actual app won't load! It just stays on that load screen, although I can see in my console, the app is loaded and running, and even the interaction are working (tap and swipe), but I can't actually see my app at all, it's just stuck on that initial screen. Any idea why?
Here's my code from my AppDelegate.m file:
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [[UIViewController alloc] init];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
//use launch image as BG image for app
UIImageView *launchScreenView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"LaunchImage-blank-grey"]];
launchScreenView.frame = self.window.bounds;
launchScreenView.contentMode = UIViewContentModeScaleAspectFill;
rootView.loadingView = launchScreenView;
rootView.loadingViewFadeDelay = 1000;
return YES;
@CorpusCallosum You can try removing the line
rootView.loadingViewFadeDelay = 1000;
or fine tune the transition between the splash screen and your app with it's value (I think the units are seconds, not milliseconds, this is why it seems "stuck" on the splash).
If removing the line completely not working for you, try setting values between 0-5 (you can set non integer values as well).
Working for me, thanks guys :)
For those who want to use their launchscreen xib as the loadingView, here is the code to insert to AppDelegate.m :
UIView *loading = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0];
rootView.loadingView = loading;
rootView.loadingViewFadeDelay = 1;
The last line is optional, as I mentioned above, it sets the time the loadingView will stay before moving on to the app itself.
@david-a thank you so much for that! I've been dealing with this since I started testing react native. 👏
@david-a, @geriux: I also had to set launchScreenView.frame = CGRectZero;
as my LaunchScreen.xib had a background image with AspectFill content mode and it wasn't scaling correctly. I got to the solution after a few trial and errors, not sure what causes this.
@BasitAli yes, sorry, that should be better documented. If you set the size of the view to nonzero it will be centered in the screen. If you set it to zero it will be scaled to fit the screen.
@david-a thank you so much! Your comment should be included in the boilerplate, or at least be a part of the main react native documentation. Not sure why this issue was closed in March 2015, it clearly still exists in RN 0.20 in Feb 2016.
It would be nice if someone can do the PR for the document.
Your guys' advices help me a lot (on IOS, working perfectly ), does anyone find a way to fix this on Android?
This any help?
@weiyisheng, I'm using react-native-splashscreen as suggested by @braco on Android along with the fix discussed above on iOS.
@braco @BasitAli YES, it helped, i will use it in my App, it's a good idea. Thank U guys.
@BasitAli why not use it on iOS out of curiosity?
My application animates in seamlessly from the splash screen to the login screen (the logo moves up and the login fields jump out). The rn-splashscreen plugin fades out which doesn't go very well along with my animation. I had to use it on Android since I had no other choice.
@BasitAli it looks like the react-native-splashscreen plugin obeys the rootView.loadingViewFadeDuration
setting, provided that you set it after you attach it, so this should disable the fade:
[RCTSplashScreen show:rootView];
rootView.loadingViewFadeDuration = 0;
Is there any way to remove that little message at the top saying "Loading from prebundled file"?
@rclai set scheme to release
Ah! Thanks!
Is there any react-native solution for both platforms splash screen? Typing in both platforms inner files makes using react-native useless.. Simple answer please. Thanks
@rclai
Is there any way to remove that little message at the top saying "Loading from prebundled file"?
That isn't shown in release mode, only in debug mode. If you want to hide it in debug mode as well, you can hide it putting [RCTDevLoadingView setEnabled:NO];
in your AppDelegate.
@nicklockwood Hi there, Is that correct? If not what should I change/delete?
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"SomeApp"
initialProperties:nil
launchOptions:launchOptions];
// Get launch image
NSString *launchImageName = nil;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
if (height == 480) launchImageName = @"[email protected]"; // iPhone 4/4s
else if (height == 568) launchImageName = @"[email protected]"; // iPhone 5/5s
else if (height == 667) launchImageName = @"[email protected]"; // iPhone 6
else if (height == 736) launchImageName = @"[email protected]"; // iPhone 6+
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
CGFloat scale = RCTScreenScale();
if (scale == 1) launchImageName = @"LaunchImage-700-Portrait~ipad.png"; // iPad
else if (scale == 2) launchImageName = @"LaunchImage-700-Portrait@2x~ipad.png"; // Retina iPad
}
// Create loading view
UIImage *image = [UIImage imageNamed:launchImageName];
if (image) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect){CGPointZero, RCTScreenSize()}];
imageView.contentMode = UIViewContentModeBottom;
imageView.image = image;
rootView.loadingView = imageView;
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
@TeodorKolev looks right to me. If you're still seeing a flash at startup, try setting
rootView.loadingViewFadeDelay
To a larger value (it's in seconds - default is 0.25)
EDIT:
This line
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect){CGPointZero, RCTScreenSize()}];
Should probably just be
UIImageView *imageView = [[UIImageView alloc] init];
Thanks, which folder should I add those images? Do I need extra Contents.json for this or not? I got multi errors in that code starting with
(https://cloud.githubusercontent.com/assets/10398337/13778676/e01e3d32-eabf-11e5-95bf-d7a1205a5ca1.png)
Staring at:
CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
@TeodorKolev Xcode has a GUI for adding them. Click on Images.xcassets, then click the + button and select "App Icons & Launch Images" > "New iOS Launch Image".
@TeodorKolev to fix the errors, you need to add
#import "RCTUtils.h"
In your AppDelegate.
Thank you a lot, but I want to see the splash screen till my WebView url fully load, and now it is not working like that. Respect!
@nicklockwood Do you have any remedy for this one?
What's the current method to fix this? Since its been a year and a few months since the problem appeared I doubt it will ever actually be fixed so I'm wondering what the current remedy is. Which file needs to be altered and which code needs to be modified?
Edit: I'm just using a black screen with the launchscreen.xib but halfway through it turns white before the app loads in full.
@hasen6 the problem has already been fixed, but there is some work needed on the part of the developer.
As discussed in the comments above, you need to set the rootView.loadingView
with some content to show in place of the white screen, and adjust the rootView.loadingViewFadeDelay
to ensure that the loading view is not hidden before your app has finished loading.
If you're just wanting to show a black screen, you might not need to bother with a loadingView and just set the the rootView.backgroundColor
to black, but you'll have to experiment.
I changed rootView.backgroundColor = [UIColor clearColor];
to rootView.backgroundColor
= [UIColor blackColor];but it didn't make any difference. Doing a search in XCode for
rootView.loadingView` didn't return any results.
@hasen6 you need to work out where the white color is coming from - it might be that there's a view inside your JS app that is white and is shown for a while before the content loads.
loadingView
is a property of RCTRootView
, which you presumably have an instance of inside your AppDelegate. The actual code rootView.loadingView
won't appear in your Xcode unless you've added it yourself. Here's an example of how you might use it:
rootView.loadingView = [UIView new];
rootView.loadingView.backgroundColor = [UIColor blackColor];
rootView.loadingViewFadeDelay = 0.5; // measured in seconds
My app is not white anywhere. All my apps show the same white screen on load after the launchscreen is shown so I'm sure its not a problem with my apps.
So I need to add that code into the appdelegate.m? That didn't do work either. I guess it must be almost impossible to fix otherwise a year wouldn't have passed with the developers still not having solved it. Considering how bad it makes apps look it would obviously be top of the list to solve too.
FWIW, this is how I'm solving this problem. I am using LaunchScreen.xib and David-a's solution:
UIView *loading = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0];
rootView.loadingView = loading;
rootView.loadingViewFadeDelay = 1;
This code is inserted just below
RCTRootView *rootView = [[RCTRootView alloc] etc etc etc...]
If I replace that code with nicklockwood's sample code the white flash returns.
Woah! Ok that one works. Thanks grittathh!
@grittathh @hasen6 ah, OK. I misremembered that loadingView was automatically sized to fit the screen if it has zero size, but apparently that was never the case ¯_(ツ)_/¯.
@hasen6 here's a modified version of the code that should work (the xib solution is fine too, but redundant if you're just using a black screen).
rootView.loadingView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
rootView.loadingView.backgroundColor = [UIColor blackColor];
rootView.loadingViewFadeDelay = 0.5; // measured in seconds
Note: 1 is quite a large value for loadingViewFadeDelay as it means users have to wait an extra second before the app starts. I suggest setting it to the lowest value you can get away with (0.25 is the default, which is fine for most apps).
Yes I found 1 was a bit long and I was missing the opening animation on one of my apps. I found even 0.1 to be fine. Thanks for your help.
I agree with @hasen6 . It certainly seems a bit strange that users have to immediately dive into Objective-C (AppDelegate.m
) and tweak some code immediately upon generating a React Native app. Wouldn't it be possible for React Native to generate code with this fix already baked in? Or to introduce a more abstract solution for start screen configuration from JavaScript (or a declarative config language)?
@johanatan Ironically the two biggest and longest lasting problems with React Native are to do with some kind of white flash. This error and the white flash on images.
When I use my LaunchScreen xib it tends to be bigger, any solutions?
What tends to be bigger?
UIView *loading = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0];
rootView.loadingView = loading;
the loading UI view, it doesn't seem to resize, its suppose to be the same as my splash screen which is my LaunchScreen.xib
I'm using size classes and constraints BTW
@akoshdee that's correct - you'll need to set the view size explicitly to the match the screen size. Using
loading.frame = [UIScreen mainScreen].bounds;
already changed the frame but still doesn't work
any solution for android?
@ajoshdee The loadingView.frame = [UIScreen mainScreen].bounds;
assignment didn't work for me but this did: loadingView.frame = self.window.bounds;
This is what I am using:
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil];
UIImageView *loadingView = [nibObjects objectAtIndex:0];
loadingView.frame = self.window.bounds;
rootView.loadingView = loadingView;
Hello! I set color to react view:
rootViewController.view.backgroundColor = [UIColor blackColor];
@py110 @ajwhite
Android solution is stupidly simple to the point of being annoyed at myself for how long it took to figure it out.
It involves a 3-stage splash screen.
I had a similar implementation @mehcode -- I like what you've got here though. I've been looking for a way to access the RN Instance manager, did not see that there was a getReactNativeHost()
call, very good to know.
I had done the following, (not happy about it)
SplashActivity
started when app opensMainActivity
and finish()
MainActivity
starts the SplashActivity
with a flag indicating it's in "foreground mode" as soon as it starts to show the splash above the MainActivity
. After a short period of time, the SplashActivity
closes itself revealing the app.@ajwhite
Thanks. The effect is very smooth.
In my current projects I quite literally have ReactActivity
copied into my codebase and have made the mReactInstanceMangager
protected instead of private.
In react-native 0.29 (should have said in the comment) the react native team gave us a public method getReactNativeHost()
to get into it.
That ReactNativeHost
container is a brilliant move. This is exactly the problem I ran into -- not being able to hook into the lifecycle from the MainActivity.
Ref for the interested: https://github.com/facebook/react-native/commit/49f20f41546e3ba8e7fe43c84c4c701684d0434d#diff-1346852de0c7f8466a36d42de50ec808R20
To add onto this...
A solution I found was to assign the rootView
a background color but use [UIColor colorWithPatternImage:img]
.
This doesn't require any delays to ensure it stays visible until fully loaded.
A full snippet (and using the tip provided by @dvine-multimedia) that will find the desired LaunchImage for the device is below:
NSArray *allPngImageNames = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:nil];
for (NSString *imgName in allPngImageNames){
if ([imgName containsString:@"LaunchImage"]){
UIImage *img = [UIImage imageNamed:imgName];
if (img.scale == [UIScreen mainScreen].scale && CGSizeEqualToSize(img.size, [UIScreen mainScreen].bounds.size)) {
rootView.backgroundColor = [UIColor colorWithPatternImage:img];
}
}
}
The LaunchImage now stays visible until the react-native bundle has fully loaded.
I've tried nearly every code block in this conversation with no chance but this one worked perfectly.
Thanks @dbonner1987 !
any solution for android?
@pareekkhushboo77 https://github.com/mehcode/rn-splash-screen/blob/master/docs/android.md
@sercanov @dbonner1987 This also uses the approach mentioned above for iOS to do the javascript launch screen.
@arnemart Thanks, still kinda works in RN 0.30.0. I had to delete my app and reinstall it. It had the white flash the first time, but none after that.
@nicklockwood Is there some sort of callback I can use to know when the actual javascript is loaded. The reason for this being, when my app is navigating between pages there is some transparency where rootView.backgroundColor is visible in the background of the view. I also get a similar problem with the Drawer plugin I'm using during the drawer open transition.
So I would like to do something like:
So I basically need a callback to know when the the loading is done.
@UKDeveloper99 Please refer to the documentation I have linked above a couple times. There is such a callback available.
https://github.com/mehcode/rn-splash-screen/blob/master/docs/android.md
@mehcode that's perfect thanks!!
Anyone have suggestions for how to implement a LaunchScreen.storyboard
that turns off once bundle has loaded? Currently it displays for a fixed amount of time then I see white screen and then application loads RN 34.1 xcode 8
@ajoshdee I had similar issues where the view ended up being much larger than the screen. Not sure if it's related but I was using AutoLayout in my LaunchScreen.xib
.
I needed to also set
launchScreenView.autoresizingMask = UIViewAutoresizingNone;
To get it to size properly.
So https://github.com/facebook/react-native/issues/1402#issuecomment-166963438 with that added line above
You can also update launch color in replace this line in appdelegate.m :
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
by this line :
rootView.backgroundColor = [[UIColor alloc] initWithRed:0.94 green:0.90 blue:0.89 alpha:1.0];
and find color you want with this website
Did someone managed the resolve this on Android ?
Here is a solution for both ios and android: https://github.com/mehcode/rn-splash-screen.
I hid the splash screen in the render function of my app.tsx (the entry point) and showed the same image until all of my https requests were done.
My code:
```
public render()
{
SplashScreen.hide();
//after everything has finished loading - show my app.
if (this.state.isFinishedloading)
{
return (
<this.navigator screenProps={{ ...providers }} />
);
}
// Until then show the same image as the splash screen with an ActivityIndicator.
return (
<View style={{ flex: 1 }}>
<Image style={styles.image} source={require('../assets/img/splash.png')} >
<ActivityIndicator style={styles.indicator} color="#fff"></ActivityIndicator>
</Image>
</View>
);
}
```
Is there any solution for this if I am using storyboard for splash screen? I don't have my launch screen images for each dimension anymore.
In react native for iOS inside AppDelegate.m
change the following line by writing your RGB color codes accordingly:
rootView.backgroundColor = [[UIColor alloc] initWithRed:52.0f/255.0f green:73.0f/255.0f blue:94.0f/255.0f alpha:1];
For RED change the number 52
For GREEN change the number 73
For BLUE change the number 94
_Note: I'm using react native v0.51.0_
Is there still no solution for Android?
@otoinsa see the above answer https://github.com/facebook/react-native/issues/1402#issuecomment-339336380 by @neomib. There are some other splash screen libraries that work just as well for e.g. https://github.com/crazycodeboy/react-native-splash-screen.
Any suggestion on storyboard used as LaunchScreen ?
My approach was like this (but the app crashes at launch)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
[Fabric with:@[[Crashlytics class]]];
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"MyApp"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [UIColor clearColor];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// UIViewController *rootViewController = [UIViewController new];
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
UIViewController *rootViewController = [storyboard instantiateViewControllerWithIdentifier:@"the_storyboard_id"];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
the app crashes with the following error:
2018-03-30 11:21:04.995601+0530 MyApp[6119:101967] Running application MyApp ({
initialProps = {
};
rootTag = 1;
})
2018-03-30 11:21:05.014109+0530 MyApp[6119:101967] *** Assertion failure in -[UIApplication _runWithMainScene:transitionContext:completion:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.33.6/UIApplication.m:3529
2018-03-30 11:21:05.090174+0530 MyApp[6119:101967] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Application windows are expected to have a root view controller at the end of application launch'
*** First throw call stack:
(
0 CoreFoundation 0x000000011170f12b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x0000000110001f41 objc_exception_throw + 48
2 CoreFoundation 0x00000001117142f2 +[NSException raise:format:arguments:] + 98
3 Foundation 0x000000010faa2d69 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 193
4 UIKit 0x000000010d838051 -[UIApplication _runWithMainScene:transitionContext:completion:] + 3102
5 UIKit 0x000000010dc016f8 __111-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:]_block_invoke + 924
6 UIKit 0x000000010dfd74c8 +[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 153
7 UIKit 0x000000010dc012f1 -
Thanks.
replaced
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
with
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil];
now the app starts but I get black background.
Or you can use https://github.com/crazycodeboy/react-native-splash-screen it's much easier :)
Most helpful comment
To add onto this...
A solution I found was to assign the
rootView
a background color but use[UIColor colorWithPatternImage:img]
.This doesn't require any delays to ensure it stays visible until fully loaded.
A full snippet (and using the tip provided by @dvine-multimedia) that will find the desired LaunchImage for the device is below:
The LaunchImage now stays visible until the react-native bundle has fully loaded.