Kotan Code 枯淡コード

In search of simple, elegant code

Menu Close

Building iOS Universal Apps with Xcode 4.x

When the iPad first came out it was a bright, shiny new toy with which developers like myself could experiment. It was great – we had all the power and flexibility of iOS and we had a massive amount of screen real estate. The list of applications designed specifically for the iPad in the App Store is testament to what developers discovered they could do with the device. I’ve used drawing apps and full-screen word processors, presentation tools, remote desktops, and one of my personal favorites – Omni Graffle.

When using some of these apps for the iPad, especially using my iPad 2, I truly feel like I’m walking around the USS Enterprise with one of those tablets of near-infinite computing power. Even with the entry of multiple “pad” competitors, the experience of using either the iPad or the iPad 2 is second to none.

Back when the iPad was new, the OS versions for the iPhone and iPad weren’t synchronized. There were features in one that weren’t in the other – even features that didn’t have anything to do with the iPad itself – the SDKs just weren’t in lock-step. This made developing applications that ran on both the iPad and the iPhone a pain – basically we had to build two apps and jump through some hoops in order to re-use code across the apps.

Then, when the versions were synchronized, we could create single Xcode projects that had multiple targets – one for iPhone and one for the iPad. It was now possible to create Universal apps but the process was still awkward and felt “off” somehow. I got that feeling I get when Microsoft releases a “beta” of some product and along with that beta is a half-assed attempt at a Visual Studio project template that invariably results in you manually hacking some XML somewhere to do what you want. The Universal app experience felt functional, but not good.

Now, with the release of Xcode 4 and up, not only does the Universal app experience feel complete, it feels freaking awesome. I’m not going to go into the technical details of building Universal apps in this blog post. In this blog post I want to go into the conceptual thinking involved in making a great universal app – things like inheritance and maximizing code re-use.

When you use Xcode 4 to create a new Universal app you’re going to get a Window-based project. To create the Universal app, start Xcode 4 and choose to create a new iOS Application. From the available templates, pick “Window-based Application” (this is the only one that has “Universal” as a device family option). Click Next. Here (see the screenshot below) you’ll see that Xcode is going to prompt you for the product identifier and to choose a device family. The default here is “Universal”.

Creating a new Universal Application

Creating a new Universal Application

The first thing you’ll notice is that we’ve got 3 app delegates. In my case, I got the following:

  • PureAwesomeAppDelegate – This is the main app delegate. This is the root class that contains functionality common to both platforms. As you’ll see throughout this blog post, this pattern is vitally important to being able to smoothly and efficiently manage the development of a universal application without turning your app into a steamy pile of spaghetti.
  • PureAwesomeAppDelegate_iPhone – This class inherits from the root app delegate and contains application-global functionality specific to the iPhone device family. Out of the box, this class has an empty implementation.
  • PureAwesomeAppDelegate_iPad – This class inherits from the root app delegate and contains application-global functionality specific to the iPad device family. Like it’s iPhone counterpart, this class begins life out of the box with an empty implementation.

Next you’ll see that we’ve got an iPhone and an iPad folder. You don’t have to adhere to this convention but I highly recommend that you do, your sanity will thank you later. Inside these folders you’ll see that there’s a XIB file for each, right next to the device-family-specific app delegate.

Looking at the screenshot below, you can see where you can now easily choose the XIB file used to start the application for each device family:

New Product Properties in Xcode 4

New Product Properties in Xcode 4

There are no view controllers here yet because it’s a window-based template. What I’ve been doing when adding a view controller to my universal apps is this:

  1. Use Xcode to create a XIB-less view controller sub-class that sits in a ViewControllers folder at the top of the product folder structure, e.g. ViewControllers/ZombieGridViewController
  2. Use Xcode to create two new view controllers and have it automatically give you a XIB file. During the wizard prompts, make sure that the class inherits from the view controller you just created, not the real base class. For example, in my case I’d create two more view controllers that inherit from ZombieGridViewController:
    1. iPad/ViewControllers/ZombieGridViewController_iPad and the corresponding ZombieGridViewController_iPad.xib
    2. iPhone/ViewControllers/ZombieGridViewController_iPhone and the corresponding ZombieGridViewController_iPhone.xib

What we’re really doing here is using an age-old pattern where we avoid littering a single class with a pile of if and switch statements by creating an inheritance hierarchy. Functionality that belongs to both device families stays at the root and only when functionality diverges do we override the base class methods in the children.

Let’s take the common example of a UITableViewController. This is probably one of the most commonly used view controllers in iOS so it makes for a good example here. When my app is running on the iPhone I want to use the stock ‘subtitle’ table view cell and on the iPad I want to create my own custom cell because when people tap the disclosure indicator I want to display a Popover, a UI element not available on the phone. Regardless of the device family using my application, the data that drives my application is going to be the same.

Let’s say then that I let my root table view controller, WeaponsListTableViewController, have some member variables that contain the data to be displayed (or, in a core data application, internal methods that talk to a managed object context to query data… but that’s a topic for another blog post).

With the data being held in a property on the root controller, each of the child controllers (WeaponsListTableViewController_iPad and WeaponsListTableViewController_iPhone) can then just override the parent controller’s tableView:cellForRowAtIndexPath: method. Both child classes would obtain the Weapon object for that row:

Weapon *wep = [[self weaponsList] objectAtIndex:indexPath.row];

And then would go about creating a view appropriate for that device family. The beauty of this pattern is we don’t have to litter our code with crap that looks like this:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  // The device is an iPad running iPhone 3.2 or later.
  // set up the iPad-specific view
else {
  // The device is an iPhone or iPod touch.
  // set up the iPhone/iPod Touch view

By using the inheritance tree to separate out just those pieces of our code that are platform specific, everything is in a clear, easy-t0-maintain place and we can spend some real thought figuring out which parts of our application are truly common to both device families and which parts need to change based on the “UI idiom”.

With Xcode 4 we don’t have to worry about trying to hack together cross-project references so we can share code and files, nor do we have to worry about all of the plumbing necessary to switch between XIBs based on the device family. All of the busywork is taken care of for us – all we have to worry about is writing our application.

Xcode 4 has single-handedly renewed my love for iOS application development and the vast majority of that love comes from Xcode 4’s brain-dead simple and extremely elegant support for Universal applications. Universal applications or multi-target applications used to be a pain and it used to be so much effort to do that you really had to want to target both device families. Now, it’s so easy to do the reasons are reverse: you really have to justify not building a Universal application.