Ad

Our DNA is written in Swift
Jump

A Quick Method to Get Launch Images

I was procrastinating creating launch images for a several of my apps until now. Apple recommends that apps should have launch images that look somewhat like the app UI, but empty so that it feels to the user like the app is starting up faster. Because of the very same laziness I put a splash screen on one of my apps.

Splash screens made a little more sense back in the days when launching an app might take around 5 seconds, of if you where using a technique to artificially prolong the display of the launch image and then have it animate away, like DTSplashExtender. In the very rarest of cases you need a splash screen if there is some legal stuff you want to get off your chest before letting the user play with the app.

Of course most professional developers would have the launch image be also created by their designer. I cannot afford such an extravagance, so I came up with the quick method I am describing in this blog post.

The ideal launch image is your normal app UI minus all buttons, images and other localized content. The strategy I am presenting here (used the first time for Summertime 1.2.1) uses a preprocessor #define to make these changes to the UI so that you can take a screen shot and then will deposit the resulting images correctly named right onto your desktop.

Step 1 – Add the #define

Any define that you would like to be global to your code can be added to your app’s precompiled header file (PCH). So, let’s add such a define.

//
// Prefix header for all source files of the 'Summertime' target in the 'Summertime' project
//
#import <Availability.h>
 
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif
 
 
#ifdef __OBJC__
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
	#import <QuartzCore/QuartzCore.h>
#endif
 
// with this define the UI gets ready for capturing the default screen
#define SCREENSHOT

I simply added the define for the SCREENSHOT to the PCH.

Step 2 – Change the UI

Now you need to think what parts will be visible on the very first screen. For this app it is a horizontal scroll view with a paging indicator, an info button in the lower right, text on the local time zone card, buttons there and the clock images.

So I went into all these places and added code like this.

- (void)viewDidLoad 
{
    [super viewDidLoad];
 
    // other setup stuff	
 
#ifdef SCREENSHOT
#warning Screenshot Mode enabled!
	self.pageControl.alpha = 0;
#endif
}

Or in the drawing of the cards:

- (void)drawRect:(CGRect)rect 
{
	// code to draw the background and border
#ifdef SCREENSHOT
#warning Screenshot Mode enabled!
	return;
#endif
...
}

Depending on the term SCREENSHOT defined you can either make existing elements invisible or omit them from drawing altogether. The extra warning pragma will output a nice warning when building to remind you to disable the flag. You might opt to leave the code in place because should you ever need to update the default images in the future you can do so without any extra work.

When you’re down you can simply comment out the define by prefixing it with //.

Step 3 – Capture the Screen

The final step is done in the app delegate. We want to render the current view (minus the elements we omitted above) into an appropriately sized bitmap context and then put the output – correctly named – onto the Desktop.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
	self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
	self.cardsViewController = [[TimeZoneCardsViewController alloc] initWithNibName:@"TimeZoneCardsViewController" bundle:nil];
 
	self.window.rootViewController = cardsViewController;
	[self.window makeKeyAndVisible];
 
#ifdef SCREENSHOT
	NSString *defaultImageName = @"Default.png";
	CGSize imageSize = [[UIScreen mainScreen] applicationFrame].size;
	CGFloat imageScale = [[UIScreen mainScreen] scale];
 
	if (imageScale>1)
	{
		if (imageSize.height<500)
		{
			defaultImageName = @"Default@2x.png";
		}
		else
		{
			defaultImageName = @"Default-568h@2x.png";
		}
	}
 
	UIGraphicsBeginImageContextWithOptions(imageSize, YES, imageScale);
 
	[self.window.rootViewController.view.layer renderInContext:UIGraphicsGetCurrentContext()];
 
	UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
	UIGraphicsEndImageContext();
 
	// thanks Cédric Luthi!
	NSString *logname = [NSString stringWithCString:getenv("LOGNAME") encoding:NSUTF8StringEncoding];
	struct passwd *pw = getpwnam([logname UTF8String]);
	NSString *homeDir = pw ? [NSString stringWithCString:pw->pw_dir encoding:NSUTF8StringEncoding] : [@"/Users" stringByAppendingPathComponent:logname];
 
	homeDir = [homeDir stringByAppendingPathComponent:@"Desktop"];
 
	NSString *path = [homeDir stringByAppendingPathComponent:defaultImageName];
	NSData *imageData = UIImagePNGRepresentation(image);
	[imageData writeToFile:path atomically:NO];
 
	NSLog(@"Image saved at %@", path);
#endif
 
    return YES;
}

Everything before the #define is standard boiler plate. A UIWindow is created, A root view controller instantiated and set as rootViewController of the window. Then the window is made key and visible. The rest is where it gets interesting.

The correct size for the image is the same size as the application frame because this includes size covered by the status bar. If you didn’t disable the status bar in info.plist then the launch image needs to be less tall.

The display scale would be 2 for retina and 1 for non-retina. I’m looking at this and the height to determine the correct output file name.

The code to determine the Desktop path comes from Cédric Luthi’s UIKit Artwork Extractor. Turns out that the iPhone Simulator can access and even write to the Desktop just fine.

That’s about it with the magic. The rest is simply creating a PNG representation and saving it to the Desktop folder.

With these things in place I needed to launch my app 3 times for the 3 resolutions and then I was done. Drag the resulting 3 new default images into my project and commit. Couldn’t be easier!

Conclusion

Be smart and set up your project such that you can generate the default images for it programmatically. The above method is quick and easy to implement but the reward is tremendous: not only will you be able to support the iPhone 5, also you will now have proper launch images just like Apple’s apps all do.

Note: You will have to have any launch image named Default-568h@2x.png in place to activate the iPhone 5 “Giraffe Mode”. If you create a new Xcode project then Xcode adds black launch images for you, so you can use this to trigger the taller mode and then replace it with your freshly generated image.


Categories: Recipes

2 Comments »

  1. Nice,
    I found a better link solution:

    https://gist.github.com/3782351