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

Comments

@alinz
Copy link
Contributor

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

This comment has been minimized.

Copy link
Collaborator

@brentvatne brentvatne commented May 28, 2015

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

@brentvatne brentvatne closed this May 28, 2015
@liubko

This comment has been minimized.

Copy link
Contributor

@liubko liubko commented May 31, 2015

@alinz

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Collaborator

@brentvatne brentvatne commented Jun 1, 2015

@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

This comment has been minimized.

Copy link
Contributor

@nicklockwood nicklockwood commented Jun 1, 2015

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link
Contributor

@nicklockwood nicklockwood commented Jun 1, 2015

@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

This comment has been minimized.

Copy link
Contributor

@nicklockwood nicklockwood commented Jun 1, 2015

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

This comment has been minimized.

Copy link
Contributor Author

@alinz alinz commented Jun 1, 2015

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

@d-vine

This comment has been minimized.

Copy link

@d-vine 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

This comment has been minimized.

Copy link
Contributor

@chirag04 chirag04 commented Jun 16, 2015

+1 for a standard/documented way.

@nicklockwood

This comment has been minimized.

Copy link
Contributor

@nicklockwood nicklockwood commented Jun 16, 2015

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

@nicklockwood nicklockwood self-assigned this Jun 16, 2015
@myusuf3

This comment has been minimized.

Copy link
Contributor

@myusuf3 myusuf3 commented Jun 21, 2015

@d-vine

This comment has been minimized.

Copy link

@d-vine 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

This comment has been minimized.

Copy link
Contributor

@chirag04 chirag04 commented Jun 26, 2015

@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

This comment has been minimized.

Copy link
Contributor

@nicklockwood nicklockwood commented Jun 26, 2015

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

This comment has been minimized.

Copy link
Contributor

@chirag04 chirag04 commented Jun 26, 2015

@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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link
Contributor

@chirag04 chirag04 commented Jun 26, 2015

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

This comment has been minimized.

Copy link
Contributor

@nicklockwood nicklockwood commented Jun 26, 2015

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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link
Contributor

@chirag04 chirag04 commented Jun 26, 2015

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

This comment has been minimized.

Copy link
Contributor

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

@ajwhite

This comment has been minimized.

Copy link
Contributor

@ajwhite ajwhite commented Jun 23, 2016

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

This comment has been minimized.

Copy link

@davidianbonner davidianbonner commented Jun 23, 2016

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

This comment has been minimized.

Copy link
Contributor

@sercanov sercanov commented Jun 27, 2016

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

@pareekkhushboo77

This comment has been minimized.

Copy link

@pareekkhushboo77 pareekkhushboo77 commented Aug 10, 2016

any solution for android?

@mehcode

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@UKDeveloper99 UKDeveloper99 commented Aug 30, 2016

@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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

@UKDeveloper99 UKDeveloper99 commented Aug 31, 2016

@mehcode that's perfect thanks!!

@comountainclimber

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@jakecraige jakecraige commented Dec 28, 2016

@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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@AdrianZghibarta AdrianZghibarta commented Aug 8, 2017

Did someone managed the resolve this on Android ?

@neomib

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor

@JakeRawr JakeRawr commented Dec 12, 2017

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.

@a3diti

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@otoinsa otoinsa commented Jan 31, 2018

Is there still no solution for Android?

@BasitAli

This comment has been minimized.

Copy link

@BasitAli BasitAli commented Jan 31, 2018

@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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@hajjiTarik hajjiTarik commented Mar 30, 2018

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)
@leonskim leonskim mentioned this issue May 23, 2018
@facebook facebook locked as resolved and limited conversation to collaborators Jul 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.