Relying On Appearances (when building a universal app)

Fri Mar 28 2014

When expanding a project from iPhone to iPad (a.k.a a universal app), many inexperienced in the area may think that it would be a no-brainer. You've already built the guts of the app and you're a competent developer so you've made things generic enough to be reused in all the right places. You've even used Autolayout in all of your screens. So porting to iPad couldn't be that hard could it?

In reality, there are several areas that need to be considered when expanding to iPad, but in this post I'd like to focus on just a few that tripped me up recently. I learn quickly from my mistakes but even better than learning from your own mistake is learning from someone else's first (it's far less expensive in all senses of the word).

Relying On Appearances

One of the biggest differences between developing for the iPhone and developing for the iPad is obviously the physical size of the device. It's held differently, interacted with differently and in particular, view objects are displayed differently. There are popover controllers and split view controllers. There are more options for how a view controller can be displayed modally. Action sheets are displayed in popovers or in the middle of the screen. All of these things can tear a great big hole in your app's logic if you have used any of the following messages to do anything important:

  • -viewWillAppear:animated:
  • -viewDidAppear:animated:
  • -viewWillDisappear:animated:
  • -viewDidDisappear:animated:
  • -viewDidLoad

First Consideration: Will It Appear?

Let's say you're using the default UISplitViewController template project and you like the idea of having a full portrait screen for showing more content. The template project (Master-Detail from Xcode's templates screen) is great for this. It even generates the code for you to hide the master view controller when in portrait orientation automatically. Nothing could be simpler!

When porting from iPhone to iPad, one approach is to take the navigation model that you've built on iPhone and expand it to better use the space for the detail view on iPad. Ordinarily, this is perfectly fine - you don't even need to make a new design for the master part of it. You just load up the iPhone-built class and it compiles, builds and even runs perfectly. A little tweak to the storyboard segue or perhaps an alternate storyboard file with minor changes and all of a sudden, you're running on iPad!

That is, unless you actually rely on something happening in that master view controller that is crucial to your app. If your app could work without that view controller loading or appearing at all, then you're sweet.

"Why is this a problem?", I hear you ask. Try starting your app in portrait orientation.

In portrait orientation, the master view controller of the split view controller does not appear until the user taps the left menu button to reveal it or exits the orientation by rotating into landscape. If you launch an important segue or present a modal view controller (like, say, a registration or welcome screen), it's just not going to work.

"That's easy", you say, "I'll just put the relevant code into viewDidLoad". Well that kinda works, but not if you're trying to present a view controller as above.

At the time the master view controller loads (assuming you are doing things by the book), the app's main window has not yet been made key and visible. That means that any attempt to execute a segue or display a modal view will fail, as the view controller executing the segue or presenting the modal view controller is not on a valid window hierarchy.

"Ah", I hear - followed shortly by, "...well how about we make the master view controller visible at least at the very start of the app?". Good thinking! I like your style! Unfortunately, that won't work either - for a different reason. Your presented view controller will display just fine and your segue will work, yes. However, when you rotate the device, suddenly your segue and your modal view controller disappear.

What's happened is that the presenting (or executing) view controller has encountered viewWillDisappear:animated: as the split view controller has dutifully hidden it, and it's been removed from the view hierarchy. Any view controller that's no longer in the view hierarchy doesn't need to have a modal presentation of any kind (apparently - I lodged a radar on this to the contrary) and so they are automatically dismissed.

To work around this particular problem, I used a few techniques. You may subclass UISplitViewController and have it deliver the events to your app, as it always loads and appears. Depending on the way you use the detail view controller, it may also be used in this way. In order to have minimal difference in the app logic between platforms, I subclassed a UIStoryboardSegue such that on iPad, it would present modal view controllers from the split view controller and on iPhone, it would present them in the usual manner, from the presenting view controller.

"But this is just an edge case for UISplitViewController! I don't even want to use that in my app!", I hear you whine. Never fear. There is more that can go wrong.

Second Consideration: Precisely WHEN will it be visible?

A good rule-of-thumb when developing for iPad is to change as little of the large display as possible as infrequently as possible. No user wants to be assaulted by huge hulking screen shifts that look just like iPhone transitions ONLY HUGE-ER-ER! We get away with it on the iPhone because the screen is small enough for these kind of transitions not to be so jarring. On the iPad, however, with this rule in mind, we tend to want to use smaller transitions and modal displays.

Popovers are an excellent example of this. They are physically not much larger than an iPhone screen and they can show and hide without being too annoying. Another example is the Form Sheet option of iPad modal presentation, which brings up a view controller in a nice plump size (slightly fatter than the iPhone size, but not too chunky) in the middle of the screen, whilst dimming everything else, UIAlert-style.

These tools and others prevent us from beating the users' eyeballs into a gritty pulp (and leaving them also without context, but that's another discussion). What they also do, is leave a lot of our view controllers - those that were previously occluded by navigation or modality - visible at times when we weren't planning on them being so.

Another example. Say you have an app that keeps track of items in a list of folders. Let's presume that the app allows users to move items from one folder to another and that ordinarily, the list of all folders is visible on-screen.

On the iPhone version, a modal view controller is displayed and you may change the item's folder from there as many times as you'd like before dismissing the modal view controller. Before the view controller is dismissed, you update the folder item counts and the display is correct as soon as the user sees it.

On the iPad, however, you've decided (with good taste) to use a Form Sheet modal option for your folder options view. You present the view controller and the problem is immediately obvious. The counts for all of the folders are visible (albeit dimmed) in the background. Using your existing design for iPhone, none of the counts will be updated until the user dismisses the modal view controller. This is not a great user experience. Especially not on iOS 7. A huge emphasis since iOS 7 has been on translucency and vibrancy - the sense of a fluid, living app. With this visible, the user needs to see the effects of their actions pretty much immediately.

Fortunately for us, the answer in this case is fairly straight-forward. Depending on your design, you may use UINotificationCenter notifications, a delegate pattern or a block pattern to let the presenting view controller (or whichever) know that it needs to update its counts. Ah-hah! We now have a much more satisfying user experience.

Preparation Advice

There are other aspects that I've omitted in this post but these are the main issues I wanted to cover. In order to be most helpful, I'd like to leave you with a list of regular expressions that I recommend running against any potential iPhone-to-iPad codebases to allow you to carefully consider how much you rely on appearances in your design and what you may have to change about that when porting to iPad.

  1. view(Will|Did)(Appear|Disappear)
  2. performSegue
  3. (push|pop)ViewController
  4. (present|dismiss)ViewController

Also, go through all storyboard files and review any non-manual segues.

Once you've done that you can sit down and review (I did it on paper) all of the logic flows that are used by these paths and either take the approach of "I do this for iPhone and this for iPad" or you may want to consider a more generic universal (and neat) approach. Be warned, though, that generic is not ALWAYS the best choice for readability or budget, so weigh up all the factors in your particular situation.

Return to list