Use Your Loaf

[[brain engage] write]

Stanford iOS 8 Course With Swift

The iOS grapevine has been buzzing the last few days with news that the latest version of the Stanford iOS course has hit iTunes U. It is once again being taught by the excellent Paul Hegarty and has been updated not only for iOS 8 but will also use Swift.

This is an interesting if not totally surprising decision as Paul mentions in the first lecture that there simply was not time to teach both Swift and Objective-C. The first two videos are already posted and immediately dig into Swift including an introduction to Swift Optionals. So whatever your level of Swift expertise I think this will be well worth following along.

You can find it on iTunes U at Developing iOS 8 Apps with Swift

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: