Saturday, February 16, 2013

UIView gotchas in iOS

In iOS the UI consists of a hierarchy of views and view controllers, linked through containment relationships. That's following the common composite design pattern, which should be familiar to most programmers. In addition there is a specific system-imposed lifecycle, which governs how all those pieces come into existence, and various callbacks that the system (Cocoa Touch) calls at specific points in this lifecycle. All this is fairly well documented, but with experience comes additional insights, and lessons learned the hard way. Here's what I discovered:

If your code relies on the coordinates of a view's frame, then your best bet is reading the frame in:

- (void)viewWillAppear:(BOOL)animated

You may be tempted to use - (void)viewDidLoad for the same purpose, which gets called earlier in the lifecycle, however at that time the frame may not have its value set accurately. I find this happening often especially for iPad. Most times I use the storyboard (and IB in the past) to implement my UIs. I usually lay it out in portrait mode only, and rely on the autoresizing mask, or layout constraints, to position everything properly. Because on iPad you can launch an app in landscape mode directly, the frame in viewDidLoad will appear as defined in the storyboard, with the size for portrait. If those values are relied upon later, that would result in problems.

This problem may also occur in obscure contexts, where one is not directly using the frame. For example in a previous post I was mentioning some weird behaviour when setting the value of a progress bar, like so:

[self.progressBar setProgress:0.5 animated:YES];

Apple took a look at it and informed me that "The view animates from an undefined state. Doing such things in the viewDidLoad can only lead to problems.".

Update:

While watching the slides from Stanford's iOS Programming course I discovered that in case that autolayout is used, instead of using viewWillAppear to get the view's geometry, one must use

 - (void)viewDidLayoutSubviews

Apparently the latter is guaranteed to be called each time the bounds of the view change, and by the time that happens all the autolayout constrains would have been processed and the geometry would have been established.