Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Initialization] Launch screen white flash #1402

Closed
alinz opened this issue May 26, 2015 · 138 comments
Closed

[Initialization] Launch screen white flash #1402

alinz opened this issue May 26, 2015 · 138 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@alinz
Copy link

alinz commented May 26, 2015

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?

@brentvatne
Copy link
Collaborator

@alinz - this is fixed in the next release: tadeuzagallo@8d99226

@liubko
Copy link
Contributor

liubko commented May 31, 2015

@alinz does it look something like https://www.dropbox.com/s/xq522ywqsd16vqe/example.mov?dl=0

@alinz
Copy link
Author

alinz commented May 31, 2015

@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.

@oblador
Copy link
Contributor

oblador commented Jun 1, 2015

@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.

@brentvatne
Copy link
Collaborator

@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

@brentvatne brentvatne reopened this Jun 1, 2015
@brentvatne brentvatne changed the title Launch Screen white flash [Initialization] Launch Screen white flash Jun 1, 2015
@brentvatne brentvatne changed the title [Initialization] Launch Screen white flash [Initialization] Launch screen white flash Jun 1, 2015
@nicklockwood
Copy link
Contributor

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 = @"LaunchImage-700@2x.png"; // iPhone 4/4s
  else if (height == 568) launchImageName = @"LaunchImage-700-568h@2x.png"; // iPhone 5/5s
  else if (height == 667) launchImageName = @"LaunchImage-800-667h@2x.png"; // iPhone 6
  else if (height == 736) launchImageName = @"LaunchImage-800-Portrait-736h@3x.png"; // 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;
}

@ide
Copy link
Contributor

ide commented Jun 1, 2015

An easy solution to what @nicklockwood described is to load the launchscreen xib if you're on iOS 8+.

@alinz
Copy link
Author

alinz commented Jun 1, 2015

@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).

@nicklockwood
Copy link
Contributor

@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).

@nicklockwood
Copy link
Contributor

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.

@alinz
Copy link
Author

alinz commented Jun 1, 2015

Thanks @nicklockwood for clarification. That was my bad. :)

@d-vine
Copy link

d-vine commented Jun 8, 2015

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.

@chirag04
Copy link
Contributor

+1 for a standard/documented way.

@nicklockwood
Copy link
Contributor

@dvine-multimedia does that work better than the code snippet I provided above?

@nicklockwood nicklockwood self-assigned this Jun 16, 2015
@myusuf3
Copy link
Contributor

myusuf3 commented Jun 21, 2015

@myusuf3

@d-vine
Copy link

d-vine commented Jun 26, 2015

@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.

@chirag04
Copy link
Contributor

@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.

@nicklockwood
Copy link
Contributor

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).

@chirag04
Copy link
Contributor

@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.

@alinz
Copy link
Author

alinz commented Jun 26, 2015

@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;

@chirag04
Copy link
Contributor

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.

@nicklockwood
Copy link
Contributor

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.

@alinz
Copy link
Author

alinz commented Jun 26, 2015

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.

@chirag04
Copy link
Contributor

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?

@mehcode
Copy link
Contributor

mehcode commented Jun 22, 2016

@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.

@atticoos
Copy link
Contributor

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: 49f20f4#diff-1346852de0c7f8466a36d42de50ec808R20

@davidianbonner
Copy link

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.

@sercanov
Copy link
Contributor

I've tried nearly every code block in this conversation with no chance but this one worked perfectly.
Thanks @dbonner1987 !

@pareekkhushboo77
Copy link

any solution for android?

@mehcode
Copy link
Contributor

mehcode commented Aug 10, 2016

@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.

@mileung
Copy link

mileung commented Aug 13, 2016

@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.

@UKDeveloper99
Copy link

@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:

  • Set the rootView background color to the launcher image, using @dbonner1987 's approach above during the loading process.
  • When the javascript has finished loading and the first navigation route starts (or something along those lines)
  • Set the rootView background color back to white.

So I basically need a callback to know when the the loading is done.

@mehcode
Copy link
Contributor

mehcode commented Aug 30, 2016

@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

@UKDeveloper99
Copy link

@mehcode that's perfect thanks!!

@comountainclimber
Copy link

comountainclimber commented Oct 13, 2016

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

@jakecraige
Copy link

@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 #1402 (comment) with that added line above

@Fantasim
Copy link

Fantasim commented Jan 26, 2017

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

@AdrianZghibarta
Copy link

Did someone managed the resolve this on Android ?

@neomib
Copy link

neomib commented Oct 25, 2017

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>
       );

   }

@JakeRawr
Copy link
Contributor

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.

@xardit
Copy link

xardit commented Jan 8, 2018

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

timothyej added a commit to blockfirm/bithodl-app that referenced this issue Jan 21, 2018
The Launch Screen is only shown until the app has loaded, not until
React has loaded. This results in a white flash between the Launch
Screen and the initial view of the app. This commit fixes that by
showing the Launch Screen until React also has finished loading.

The solution was found here:
facebook/react-native#1402 (comment)
@otoinsa
Copy link

otoinsa commented Jan 31, 2018

Is there still no solution for Android?

@BasitAli
Copy link

@otoinsa see the above answer #1402 (comment) 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.

@rimzici
Copy link

rimzici commented Mar 30, 2018

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.

@rimzici
Copy link

rimzici commented Mar 30, 2018

replaced

UIStoryboard *storyboard = self.window.rootViewController.storyboard;

with

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil];

now the app starts but I get black background.

@hajjiTarik
Copy link

Or you can use https://github.com/crazycodeboy/react-native-splash-screen it's much easier :)

timothyej added a commit to blockfirm/pine-app that referenced this issue Apr 7, 2018
The Launch Screen is only shown until the app has loaded, not until
React has loaded. This results in a white flash between the Launch
Screen and the initial view of the app. This commit fixes that by
showing the Launch Screen until React also has finished loading.

The solution was found here:
facebook/react-native#1402 (comment)
@facebook facebook locked as resolved and limited conversation to collaborators Jul 22, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests