One of the first hurdles you hit when getting started with developing apps for the iPhone and iPad is handling orientation changes. At first it’s so easy; the auto-sizing does the job for you nicely but then you want to do something more complicated and you find it doesn’t work in all places (Arrgghh). To make sense of this I set up a little project (available on github) that does a ton of logging when events happen to work out the best ways to proceed.

There are a whole bunch of places you can hook in the initial code for laying out your view. Some of them are better than others: here’s a run down of methods called

[UIViewController loadView] - Only called when view controller is created and only when done programitcally. Bounds are incorrect (normally empty) at this point.
[UIViewController viewDidLoad] - Only called when view controller is created. Bounds are incorrect (normally empty) at this point.
[UIViewController viewWillAppear:] - Called whenever the view is presented on screen (although not necessarily when app returns from background state). We have bounds at this point however the device orientation hasn’t been corrected so these might be for portrait when we are in landscape (or vice versa).
[UIViewController viewWillLayoutSubviews] - This is the first method where we have the correct bounds for the device and is called whenever the view controller is changing the display of the view (like device orientation, view loaded) or the UIView is marked as needing a layout
[UIView layoutSubviews] - This method is called on the UIView rather than the UIViewController. It has the correct bounds. Great place to change layout.
[UIViewController viewDidLayoutSubviews] - Called after layoutSubviews is called on UIView. Changes made in layoutSubviews can be animated, further changes made in this method can cancel the animations.
[UIViewController viewDidAppear:] - Called once the view has appeared. It has the correct bounds but changes called here can happen after an animation has occurred and cause a slight jump.
If we were to launch the app in landscape we’d find before viewWillLayoutSubviews the following method is called:
[UIViewController willRotateToInterfaceOrientation:duration:] - Incorrect bounds
And after viewDidLayoutSubviews:
[UIViewController willAnimateRotationToInterfaceOrientation:duration:] - Correct bounds, changes in here will animate
[UIViewController didRotateFromInterfaceOrientation:] - Correct bounds

From this we have a view places where we can do our laying out, but once we consider a couple more cases the amount of places decreases

When rotating the device the following methods are called:
[UIViewController willRotateToInterfaceOrientation:duration:]
[UIViewController viewWillLayoutSubviews]
[UIView layoutSubviews]
[UIViewController viewDidLayoutSubviews]
[UIViewController willAnimateRotationToInterfaceOrientation:duration:]
[UIViewController shouldAutorotateToInterfaceOrientation:]
[UIViewController didRotateFromInterfaceOrientation:]

And when presenting a view controller we get the following:
[UIViewController loadView]
[UIViewController viewDidLoad]
[UIViewController viewWillAppear:]
[UIViewController shouldAutorotateToInterfaceOrientation:]
[UIViewController viewWillLayoutSubviews]
[UIView layoutSubviews]
[UIViewController viewDidLayoutSubviews]
[UIViewController viewDidAppear:]

And finally dismissing a view controller (when orientation was changed in the child view controller):
[UIViewController viewWillAppear:] - Note correct bounds here
[UIViewController viewWillLayoutSubviews]
[UIView layoutSubviews]
[UIViewController viewDidLayoutSubviews]
[UIViewController viewDidAppear:]

This actually narrows us right down to the point where we only have 2 safe choices if we want to be able to animate the transition and have the view laid out correctly across all the orientations.

The first approach is to use the layoutSubview method on the UIView.
Pros of this technique:
+ Called every time the view is loaded or orientation changed and can be triggered
+ Separates the concerns of dealing with the positioning of subviews to be within the view meaning cleaner view controllers :)
+ Changes done here will animate if animating a rotation
- Needs a subclassed UIView each time, which is another class to create.
- You don’t have direct access to the orientation of the device here, you can use the bounds for laying out or if you want the device orientation you can access it via the static method [[UIApplication sharedApplication] statusBarOrientation]

The second approach is to use viewWillAppear and willAnimateRotationToInterfaceOrientation:duration: on the ViewController
+ All code can be in the UIViewController so no need to subclass the UIView
+ Handles animation
- You need to create an extra method that both methods call or duplicate code
- Will often mean the interface is laid out twice for a single interface, shouldn’t really be a performance problem unless you’re doing a ton of stuff to work out the lay out.

This concludes the complications of orientation changes. No doubt there are some scenarios I haven’t considered but hopefully it will help someone out. If not It helped me at least :-) Again if you want the source it’s on github.

  1. kevindew posted this