Handling Subviews Of The Main View In UIViewController: Part 2 (iPhone)

In part 1 of this blog, we created an app that has a single view controller that dynamically creates two subviews with a single button each. We used delegation through a protocol to respond to the button touches, but the view controller isn’t responding to the delegate methods. The source code for part 1 is available here:[link to part 1 source code here].

In order to diagnose the problem, let’s review how we’re setting up the view controller. In initWithNibName: bundle, we instantiate the two views and make them subviews of the view controller’s view:

 

 

 

We know this code works, because the views display properly when the app is run:

In viewDidLoad, we set self (this ViewController) to be the delegate of each view:

 

 

 

This seems reasonable, since the view controller’s init method should be done by the time the view loads. But wait… what view is referred to in viewDidLoad? View1? View2? Some other view?

The correct answer is “some other view.” All view controller’s have a view. This view controller’s view is the one declared in ViewController.xib (which is empty). ViewDidLoad is a delegate method: it will be called when the ViewController’s main view is loaded, regardless of whether initWithNibName: bundle: is done. In this case, viewDidLoad is attempting to set self to the delegate property of each view before the views have been instantiated! Since view1 and view2 are uninitialized, the code in the view controller is equivalent to saying:

 

 

 

(not really, but this gives a good mental approximation, since uninitialized object properties are basically nil.)
So how do we fix this? In this case, the solution is simple: move the delegate setting code from viewDidLoad to initWithNibName: bundle:

 

 

 

Now run the app, click the buttons, and note the result:

The first message from each button is from the action method in each view, the second is from the view controller’s implementation of the delegate method.

Alternatively, we could move all code inside initWithNibName: bundle: to viewDidLoad, and do away with the override of initWithNibName: bundle: altogether. The point is that we should always ensure that objects are properly allocated and initialized before attempting to set or get properties of those objects.

Perhaps the best way to ensure that objects are created before being used is “lazy instantiation.” In this approach, we override the getters for properties to alloc/init them if they do not already exist. Here is a new ViewController.m that dispenses with initWithNibName: bundle: in favor of lazily instantiating view1 and view2:

 

 

 

When viewDidLoad binds the delegates to self, the getters are called in the view objects, and the app now runs properly:

Leave a Comment:

1 comment
Josh Brown says July 8, 2013

Thank you so much for this tutorial. This was extremely helpful! I worked through the full tutorial and everything worked.

I am now using this approach to build an app which contains different subviews each with a different set of buttons. I have a question. How could I modify this approach to enable me to design my subviews in IB? I have tried by adding a class that is a subclass of UIViewController (rather than subclassing UIView) so that i get the xib file. But when I follow the steps i get stuck with how to “connect” the button. I use a UIAction, so presumably i don’t need the “addTarget” etc. But I cannot get it to work.

Any tips would be greatly appreciated.

Reply
Add Your Reply