Figuring out Launch Images in iOS

If you are not familiar with iOS launch screens, they appear immediately when an application is started. The launch screen is then quickly replaced with the first screen of your application, which can give the impression that your application is fast and responsive.

react native, iOS, tutorial

05-04-2018

Table of contents

two phones side by side - one showing the app, another the launch screen

If you are not familiar with iOS launch screens, they appear immediately when an application is started. The launch screen is then quickly replaced with the first screen of your application, which can give the impression that your application is fast and responsive.

Consolidating Apples Human Interface Guidelines, they state the launch screen should not be used as an artistic expression or as an advertisement opportunity, however it should enhance the perception of your app as quick to launch.

This essentially means that one wants to keep the launch image as close as possible to how the first screen of your application, while making it as simple as possible to not draw to much attention to the launching experience.

Lets dive in!

I am going to be splitting this up into 3 separate steps. First, I will show how to design the right sized image assets. I will also provide a free Sketch file to ease the process for you!

Second, I will go through how to add them to your iOS project in Xcode and how to configure the project to use the Launch Images as source.

Finally, I will end off by making some programmatic tweaks to the iOS root view, allowing us to have the Launch Image stay until the React root view is initialised, making sure that your users won't experience any unpleasant flash.

TL;DR

if (Platform.OS === 'iOS)

  1. Download my Sketch file for 1 click assets export
  2. Clear reference to the Launch Screen File in Xcode
  3. Add new Assets Catalog and drag in the exported assets

if (Platform.OS === 'react-native)

  1. Do the above steps for iOS
  2. Create a new UIView to use as the main view
  3. Loop through assets to create a UIImageView with the launch image (check the scale)
  4. Add a subview to the new UIView
  5. Add React rootView to the top of the view stack after everything is initialised

1. Designing the Launch Images

If you are anything like me, adding a set of Launch Images ot your application is a huge pain. A process that should essentially be straight forward, is increasingly confusing with Apples interesting naming convention in Xcode and even if you are familiar with Apples way of naming required assets, making it actually work is still a little tricky.

What pixel values does Retina 4.7'' correspond to? What about 2x? If you don't know what these names translates to in pixels, it is hard to even get started designing your Launch Image assets. To ease this process, I have created a Sketch file that contains all the required sizes, so all you have to do is create your design there, and export all the assets with the correct sizing in one click — you can download the Sketch file here

launch images for all iPhone sizes in a row

2. Configuring Xcode

Now that you have designed your Launch Images and exported all the right sized image assets, it is time to get them into Xcode and configure your project to use them.

When you open your Xcode project file and go to the General tag, you can locate the section named App Icons and Launch Images. In this section, you want to make sure that you clear the Launch Screen File input field. This input field will initially be set to use the default launch storyboard, but in this tutorial we are not utilising that, but rather using the Launch Images.

showing Xcode UI for the above section

After clearing the Launch Screen File input, you want to create a new Asset Catalog which adds a folder named Images.xcassets in your main project folder. If you open this folder, you will see the LaunchImage section. In the Utilities area of Xcode (right hand panel) you can open the Attributes Selector to specify what types of devices you want to support. If you are not planning on making you application available on iPad or in portrait mode, you can choose not to select these, which means you only have to supply the iPhone specific assets.

You can now simply drag and drop the exported assets from Sketch into their right places in Xcode.

showing Xcode UI for the above section

3. React Native

If you are a React Native developer and want the Launch Image to last till the React root view has been initialised to avoid the white flash that happens in between the Launch Image terminating and the React root view initialising, all you have to do is follow along here.

We are going to d a little bit of coding trickery to make this work. A simple solution would simply change the background colour of the root view, but since we are using a Launch Image with a "fake" header, you will still experience a flash in the UI.

If you want to get rid of that abrupt flash completely, we will be adding a few extra steps:

First, we are going to allocate a new background view in which we are going to add our image. We do so by looping through our assets to find the LaunchImages. We however need to make sure that we choose the right sized image for the specific device resolution.

When we have the right sized LaunchImage stored in an UIImageView, we can add that as a subview to the background view we created earlier, which will automatically push it to the top of the view stack.

After React has initialised and the root view is loaded, we add the React rootView as a subview of the background view we created to push it on top of the view stack.

#import "AppDelegate.h"
#import <React/RCTPushNotificationManager.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import	<React/RCTLinkingManager.h>
#import <Fabric/Fabric.h>
#import <Crashlytics/Crashlytics.h>
#import <FBSDKCoreKit/FBSDKCoreKit.h>
@import GoogleMaps;
@import UserNotifications;
@import Firebase;

@implementation AppDelegate

- (void)applicationDidBecomeActive:(UIApplication *)application
{
	[FBSDKAppEvents activateApp];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
	NSURL *jsCodeLocation;
	[Fabric with:@[[Crashlytics class]]];
	[FIRApp configure];
	[GMSServices provideAPIKey:@"AIzaSyCbj7DAEESa1bk6Eid60EgC2o2LQPwqL6I"];
	[[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];

  // DEBUG: Change the default code location to point to the static bundle for release
//      jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
//    jsCodeLocation = [NSURL URLWithString:@"http://127.0.0.1:8081/index.bundle?platform=ios&dev=true"];
//    jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];

  #ifdef DEBUG
    // DEV
    jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  #else
    // PROD
    jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
  #endif

	// Create the backround UIView, allocate it in memory and initialise it
	UIView *backgroundView = [[UIView alloc] init];
	// Create an array with all the png image resources
	NSArray *allPngImageNames = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:nil];
	// Loop through the images
	for (NSString *imgName in allPngImageNames)
	{
		// Find the launch images
		if ([imgName containsString:@"LaunchImage"])
		{
			// This is a launch image
			UIImage *img = [UIImage imageNamed:imgName];
			// Check for scale and dimensions as current device
			if (img.scale == [UIScreen mainScreen].scale && CGSizeEqualToSize(img.size, [UIScreen mainScreen].bounds.size))
			{
				// Create an image view, allocate it in memory and initialise it with the image that we have found
				UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imgName]];
				// Add the image view to the top of the stack of subviews
				[backgroundView addSubview:imageView];
			}
		}
	}

	RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
		moduleName:@"ShokiMap"
		initialProperties:nil
		launchOptions:launchOptions];

	//  rootView.backgroundColor = [[UIColor alloc] initWithRed:0.93 green:0.93 blue:0.88 alpha:1.0];
	rootView.backgroundColor = [UIColor clearColor];
	// Animations
	rootView.loadingViewFadeDelay = 0.0;
	rootView.loadingViewFadeDuration = 0.15;
	self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
	UIViewController *rootViewController = [UIViewController new];
	// Set the backgroundView as the main view for the rootViewController (instead of the rootView)
	rootViewController.view = backgroundView;
	self.window.rootViewController = rootViewController;
	[self.window makeKeyAndVisible];
	// Add the rootView as a subview to the backgroundView, making it top of stack
	[backgroundView addSubview:rootView];
	// Make the rootViews frame the same as backgroundView
	rootView.frame = backgroundView.frame;


	return YES;
}