Use Your Loaf

[[brain engage] write]

Using a Launch Screen Storyboard

Static Launch Images

Launch images are what iOS displays whilst loading an App to give the impression of a responsive system. Creating these static launch images for the growing number of screen resolutions has become something of a pain in recent years. At the time of writing the list of possible launch image sizes is below (sizes include the status bar region). I have omitted the landscape versions for brevity:

  • iPad 2 and iPad mini (@1x): 768 x 1024
  • iPad and iPad mini (retina @2x): 1536 x 2048
  • iPhone 5 (@2x): 640 x 1136
  • iPhone 6 (@2x): 750 x 1334
  • iPhone 6 Plus (@3x): 1242 x 2206

There is some good news with Xcode 6 and iOS 8 which allow a NIB or storyboard launch screen file to be used. By taking advantage of auto layout and size classes a single NIB or storyboard file automatically creates the launch images at runtime. Note that if you want to properly support the larger iPhone 6 and iPhone 6 Plus screen sizes in fullscreen mode without scaling you must supply the appropriate launch images either as static images of dimensions as listed above or with a storyboard launch screen file.

(Updated 26-Dec-2014: made it clearer that supporting iPhone 6 and iPhone 6 Plus requires you to provide the launch images but they can be static image files or from a launch screen file).

Using a Launch Image File

Xcode 6 adds a LaunchScreen.xib file by default to new projects. For an existing project add a new file using the Launch Screen template:

Launch Screen Template

Note that this will add a NIB file to the project which is fine if you have a single view or view controller on the initial launch screen. If you have multiple views you will need to ignore the launch screen template and add a storyboard. You should also specify the launch screen file in the project settings for the target:

Project Settings

This will add the Launch screen interface file base name (UILaunchStoryboardName) key to the application plist file:

UILaunchStoryboardName

At this point you can layout the launch view in Interface Builder using autolayout and size classes as necessary to create suitable images for each screen resolution. The Xcode template does not provide a very good example as it provides a splash screen style layout with the app name and copyright statement that you will probably want to delete before adding your own view layout:

LaunchScreen.xib

You can preview the storyboard in Xcode or test it in the simulator or on an actual device. Since the launch screen is only briefly displayed you may find it useful to set a breakpoint on application:didFinishLaunchingWithOptions: in the App delegate.

Launch Screen Constraints

The system loads the launch file before launching the app which creates some constraints on what it can contain (some of which may force you back to static image files):

  • The app is not yet loaded so the view hierarchy does not exist and the system can not call any custom view controller setup code you may have in the app (e.g. viewDidLoad)

  • You can only use standard UIKit classes so you can use UIView or UIViewController but not a custom subclass. If you try to set a custom class you will get an Illegal Configuration error in Xcode.

  • The launch file can only use basic UIKit views such as UIImageView and UILabel. You cannot use a UIWebView.

  • If you are using a storyboard you can specify multiple view controllers but there are again some limitations. For example you can embed view controllers in a navigation or tab bar controller but more complex container classes such as UISplitViewController do not work (at least not yet).

  • Localizing the launch file does not currently seem to have any effect. The base localization is always used so you will probably want to avoid text on the launch screen.

  • You cannot specify different launch files for iPad and iPhone. This may be a problem if you have significantly different interfaces for those devices as there is only so much you can do with auto layout and size classes.

Note that if you are deploying to iOS 7 you will still need to include the static launch image files. You can include both a launch image file and static launch images. Devices such as the iPhone 6 running iOS 8 will use the launch image file whilst iOS 7 devices will fallback to the launch images.

Split View Controllers

If your root view controller is a split view controller you do not have too many options at least with iOS 8.1. If you add a split view controller to the launch screen storyboard it will not load. The increased flexibility of split view controllers in iOS 8 also makes me suspect they will not be supported any time soon.

Other than going back to static launch images the only alternative seems to be to simplify the user interface by ignoring the split screen. For example consider the following iPhone and iPad launch screens that use a split view controller. On the iPhone (compact width) device the initial screen shows the master view controller (a table view controller embedded in a navigation controller in this case):

Initial Screen iPhone

On the iPad (regular width) device the initial screen after launch shows the master and detail view controllers in a split screen layout:

Initial Screen iPad

This is a very common setup but there is no good way to use a Launch screen file in this case. I am open to suggestions but the closest I can get is to ignore the split screen and use a view controller embedded in a navigation controller as the launch screen.

Launch Storyboard

It is far from perfect but it does as least more or less match the initial user interface on all devices (albeit without the split on the iPad). As a launch image intended to give the user the impression that the app is loading it may just about be good enough but you will have to judge for yourself.

Runtime Generation (added 28-Dec-2014)

The Apple documentation does not make it clear but the required launch images are generated by the system at runtime. This was briefly mentioned in the WWDC 2014 Platform State of the Union session (at about 1hour,22minutes). You can verify it by looking at the application container of an App deployed to a device or the simulator. The launch images required for a specific device are cached in Library/Caches/LaunchImages. The following screenshot shows the launch images generated on an iPad Air 2:

For comparison the following screenshot shows the launch images generated for an iPhone 6 Plus:

Modules and Precompiled Headers

Apple announced Modules in WWDC 2013 so this post may be a little over due. What prompted me was noticing that Xcode no longer adds a Prefix.pch file to new projects by default which has a lot to do with modules.

#Import versus #Include

To understand why modules are useful it is worth first recapping the traditional way of working with frameworks and libraries. As I am sure everybody reading this knows it involves including (via a #include) the framework or library header in your source file and then at build time linking with the library.

The #import directive used by Objective-C is almost identical to the classic #include mechanism it inherited from the C programming language. The only difference is that it makes sure the same file is never included more than once. This is a small convenience to avoid the common C technique of including a header guard for the preprocessor:

/* someframework.h */
#ifndef _SOMEFRAMEWORK
#define _SOMEFRAMEWORK
/* body of someframework.h */
#endif

Unfortunately Objective-C did nothing to address other problems with the #include preprocessor mechanism. Having the preprocessor parse each header file in each source file quickly becomes slow and inefficient as the project grows as many source files include the same header files. The simple mechanism of text inclusion also leads to fragility as names from different headers collide.

Precompiled Headers

Precompiled headers are a partial solution to the problem of slow build times. They speed up the time it takes to compile a project when all or nearly all source files need to include some common headers. For example, an iOS project is likely to include <UIKit/UIKit.h> in most if not all source files. This means parsing and compiling the UIKit.h header many times when building the project which is wasteful and slow. A precompiled header file, as the name suggests, collects the common headers into a separate file. Precompiling this file just once and then automatically including it in all source files in the project significantly speeds up the build process for many projects.

Here is a typical Prefix.pch file from an old project that imports UIKit and Foundation headers as well as checking for at least iOS 5:

#import <Availability.h>

#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
#endif

The ability to have something globally included by placing it in the pch file is a useful but easily abused facility. Maintaining and optimising the prefix header file also puts a lot of the work on the developer that the tools should be handling for us. For another view on the ways developers can abuse prefix headers see 4 Ways Precompiled Headers Cripple Your Code.

Modules

A module offers a better way to work with system frameworks and libraries by replacing the preprocessor text inclusion mechanism with what Clang refers to as a semantic import. To import a module you use the @import declaration instead of the #include or #import preprocessor directives (note the semicolon):

@import UIKit;

When the compiler sees a module import it loads a standalone precompiled version of the framework. It also automatically takes care of linking to the module meaning you no longer need to manually add the framework in Xcode. Since the module is compiled just once there is no longer any advantage to including the framework header in prefix.pch. As a result the Precompile Prefix Header build setting now defaults to NO in Xcode.

Using Modules

Opting into using modules is as easy as setting the Apple LLVM 6.0 - Language - Modules Xcode build settings:

Both the Enable Modules and Link Frameworks Automatically settings default to YES in new Xcode projects. In fact once modules are enabled any #import or #include directives are automatically converted to @import declarations. This means you can adopt modules without having to make source code changes. For example a header file that has old style preprocessor imports for UIKit and Core Data frameworks:

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

With modules enabled this is automatically mapped to import declarations:

@import UIKit;
@import CoreData;

As a result of this automated conversion and the new Xcode defaults you may even be using modules without realising it if you have recently created a new project. One limitation of modules is that they are not available for user frameworks but the Apple system frameworks have been available as modules since iOS 7 (and OS X 10.9).

Adding a Pre-Compiled Header to a Project

Now that modules are available there is no need to continue to list system frameworks in a precompiled prefix header. If you need to add a prefix header to an Xcode project you can still do that (at least at time of writing with Xcode 6.1) by manually modifying the Precompile Prefix Header flag to YES and specifying the path in Prefix Header:

Further Reading

Some links for further reading:

Continuous Integration With Xcode Server

Xcode Server was introduced with Xcode 5 to provide a way to perform continuous integration through the creation of bots that automate the building, analysing, testing and archiving of projects. The bots can be created in Xcode on a development Mac and then run on the server. Since this requires OS X server it is a great way to use a spare Mac you have gathering dust.

This post collects together my notes on installing and setting up the Xcode 6 service on a Yosemite OS X Server. My simple objective was to have my unit tests run each time I make a commit to a project hosted in a GitHub repository.

Installing OS X Server

To install Xcode Server you first need a Mac running OS X Server. There is nothing to stop you from turning your development Mac into a server and using it to run Xcode Server. However it is also a great way to use an old Mac as long as it is capable of running Yosemite.

If you are a member of the iOS developer program you probably know that you are entitled to use Mac OS X Server free of charge. To redeem your copy you need to login to the iOS Dev Center, find OS X Server in the downloads section and request a download code:

OS X Redeem Code

At the time of writing there are two versions of OS X Server available depending on whether you use OS X Mavericks or Yosemite. For the rest of this post I am assuming OS X Server 4.0 running on Yosemite.

If you are already on the Mac where you want to install the server you can just click on the redeem code in the iOS Dev Center to directly launch the Mac App Store, redeem the code and start the download. If you need to enter the code manually the Redeem link is listed under Quick Links in the Mac App Store. The Server application downloads to the Applications folder and needs to be run to complete the setup.

Server Icon

Click the continue button, enter your password for admin access and let the install complete. Once the Server app is displayed you should the list of services that you can now run on the server in the sidebar (none of which are currently enabled).

Services

Remotely Managing A Server (Optional)

If you have multiple Mac’s and want to administer the server remotely you can download the Server app from the Mac App Store to another machine. To allow remote management you need to check the server settings and ensure that the “Allow remote administration using Server” is enabled:

Server settings

Now when you run the Server app on the remote Mac select “Other Mac” and enter the host name or IP Address of the server together with the Administrator name and password to connect.

Server setup

Since the server is running with a self-signed certificate that was generated during the installation you will most likely get a warning. You can either just accept the certificate this time by clicking “Continue” or select “Show Certificate” to modify the trust. You can choose to always trust “com.apple.servermgrd” or only this certificate:

Verify Certificate

Turning On Caching Service (Optional)

One other tip you can take advantage of is to turn on the Caching service. This speeds up software downloads and updates by caching Mac and iOS system updates and App Store downloads on the server. To enable click on the Caching service in the Server app and switch it on. You can optionally select the disk volume and cache size. Once the service is running any clients (Mac or iOS) on the same local network will automatically use the caching service.

Server caching

Configuring the Xcode Service

With OS X Server installed and running we can turn our attention to the Xcode Service. If you have not already done so you should download and install Xcode 6 on the server from the Mac App Store (assuming you are developing with the released version of Xcode). If you have just installed Xcode on the server you will need to run it before configuring the Xcode service to accept the software license agreement and let it finish installing components.

Click on the Xcode service in the sidebar of the Server app and then use the “Choose Xcode” button to find the version of Xcode you want to use (for example: /Applications/Xcode):

Once the setup finishes you will need to slide the switch to start the service:

We now have a running Xcode service but before we look at using it from a project there are a few extra server configuration steps:

  • To allow Xcode Server to access provisioning profiles you need to add a developer team from the Apple Developer program. Use the Add… button and enter your Apple Developer credentials to confirm you want to add this server to your team.

  • Any test devices you attach to the server should show up in the devices list. If this is the first time you have used the device for development click the “Use for Development” button and then “Add to team” to add it to the Apple developer team. If the device shows as not supported check you have removed any passcode lock.

  • You can also configure Xcode server to host code repositories on the server but since I am going to be using a remote repository hosted on GitHub I will skip this step.

Configure Xcode Development Environment

The following steps are performed on the development machine. I am assuming you already have a project with unit tests.

Add Server Account

On your development Mac add a server account to Xcode so that you can check the status of the remote bots from Xcode. In the Xcode preferences Account tab use the Add button and add a server. The server should show up in the list, enter your user name and password to connect to the server.

Share The Scheme

The Xcode project scheme defines the settings to use when a project is built, run, tested, profiled, analysed or archived. To allow these settings to be accessed by the Xcode server they need to be checked into the source code repository which requires that they be shared. In Xcode on the development machine use Product > Scheme > Manage Schemes to view the available schemes. In my case I have only a single scheme to mark as shared:

This creates the xcshareddata directory containing the scheme configuration that I can then check into the Git repository for the project.

Add Remote Repository to Xcode Accounts

To allow the Xcode Server to access the remote code repository you first need to add it to the Xcode Account settings on your development Mac. In the Xcode preferences Account tab use the Add button to add the repository. In my case I want to access a repository on GitHub using SSH public/private keys so I will use the SSH URL for the repository as follows:

[email protected]:kharrison/UYLPasswordManager.git

Note that there is no way, that I can see, to enter the SSH keys for authentication at this point.

Setting Up SSH Keys For Repository Access

To allow the Xcode server to access the GitHub repository it needs to know the SSH keys to use. GitHub has detailed instructions on generating SSH keys that you can follow if you need to create keys. In brief to create a new pair of keys from the command-line:

$ ssh-keygen -t rsa -C “[email protected]

You will prompted for a file name and then a passphrase. By default this will create two files in the .ssh subdirectory of your home directory named id_rsa and id_rsa.pub but you can use some other value when prompted. You need to copy the contents of the id_rsa.pub file to the SSH keys section of your GitHub account. If in doubt refer to the detailed GitHub instructions.

A quick tip that I always forget: The pbcopy command is a quick way to copy the contents of a file to the clipboard making it easy to paste into a window:

$ pbcopy < ~/.ssh/id_rsa.pub

Create a Bot

To create a bot to run our unit tests, use Product > Create Bot and check the scheme and server name are correct:

At this point the Xcode Server will check if it can access the source code repository which in my case will fail as I have not given Xcode any credentials:

Use the Change button and select Existing SSH Keys for the type and paste in the public (id_rsa.pub) and private (id_rsa) keys into the correct fields

Clicking Next will cause Xcode to validate the credentials and if all went well you should see the options to schedule and configure the bot:

Configure the Bot

A bot can be configured to run hourly, daily, weekly or when there is a new commit to the repository. When run the bot can perform static analysis, testing and product archiving actions. You can also specify if and when the bot should clean the build products and intermediates. To fit my requirements to run the tests on each commit I selected the following options:

  • Schedule: On Commit - the bot will check for new commits every 5 minutes
  • Actions: Perform analyze action, Perform test action
  • Cleaning: Once a day

I don’t archive every build and I only clean the build products once a day to keep the builds fast. Since you can have multiple bots you could also for example schedule a bot to run once a day that did a clean build and archive.

Next you can specify which devices and simulators should run the tests. Any devices you want to specify must be connected to the server. If you select Specific Devices and Simulators you are presented with a list of attached devices and the long list of simulators for each version of the iOS SDK.

Finally you can add an email notification or shell script trigger before and after a bot runs if you need some custom actions. By default an email notification is generated when a test fails or a build has errors:

With the bot configuration complete, use the Create button.

Monitoring Bots

There are several ways to view and manage a bot:

Report Navigator

You can manage the bots you have created from the Report Navigator in Xcode on your development Mac:

Right-clicking the bot in the navigator pane allows you to edit or delete the bot or run it immediately. Selecting the bot will show a summary of the bot integrations in the editor area of Xcode. Clicking on the entry in the navigator pane for a specific integration run will show detailed results:

Web Browser

Each bot is also accessible via a web browser at the URL: <hostname>/xcode/ where <hostname> is the address of the OS X Server. This view is less useful than the Xcode reporting interface but it does provide a quick summary of the most recent integrations. You can also switch to the big screen mode either by clicking the button on the bots homepage or using the URL: <hostname>/xcode/bigscreen.

Closing Summary

If you have never used a continuous integration server to run your tests you should give Xcode Server a try. It is a little bit of work if you are not already running OS X Server but for the most part it is simple to get started. Alternatives such as Travis CI are free for open source projects but will cost you money for private repositories so it is worth trying Xcode first to see if it will meet your needs.