Reflections on one month of iOS development: Part II
February 14th, 2012 by acwOr, “An intro to iOS/Objective C/Cocoa Touch for people from other programming backgrounds”. This is part II of II. Part I is here.
Now that you’ve gone through part 1 of this series, you should feel that your comprehension of Objective C is coming along, but you’re still wondering how to relate that knowledge to making iOS apps. Great – let’s start turning on the light. We’ll continue to use the source of Paolo Quadrani’s Ecological Footprint app as our example of a working iOS app. Let’s get started:
-
If you recall from part 1, I said that you can think of the AppDelegate as the entry point to an iOS application. That’s only sort of true – the true entry point is the main.m file (generated by Xcode), which contains a single function that will be very familiar to anyone who’s ever looked at an application built in regular old C: int main (int argc, char *argv[]). This is where our app’s lifecycle begins. The real magic of this function is on line 14, with the call to the UIApplicationMain function. While I’ll leave the details of that call for this excellent discussion, the basic gist is this: the nil arguments tell the function to look for a .plist (Property List XML) file, which contains a key called NSMainNibFile. The value of that key points to an Interface Builder file (.xib), which has a different key that articulates where to find an AppDelegate. In practice, you really can treat the AppDelegate as your entry point, but it’s still worth having the full picture.
-
So let’s get back into the AppDelegate. Open up the EcoFootprintAppDelegate header and implementation files again. You’ll see that in the interface for the AppDelegate, there’s a member variable of type UIWindow. An AppDelegate must create a UIWindow instance and populate it with views – that’s the delegate’s fundamental purpose. Where does it do that? In the UIApplicationDelegate protocol’s (void)applicationDidFinishLaunching:(UIApplication *)application method. What’s the UIApplication instance that gets passed in? That’s created by the call to UIApplicationMain described above, and is basically the overarching object that controls the event routing within an iOS app.
-
So how does the applicationDidFinishLaunching: method create its views? By creating instances of those views’ controllers and adding them to the window object through [windowVariable addSubview: [controllerVariable view]]; Let’s walk through it. First, notice on line 11 that the MainViewController.h header is included – we’ll need that to create instances of the controller. Line 50 is where the instantiation takes place. Down on line 61, that instances is used to place the controller’s view in the window. Line 62 then makes the window the main window and causes it to be visible. Voila – your controller’s view is now front and center.
-
So let’s dig into what makes those views tick. When you add a new view to a project, you’ll typically create three files: an Interface Builder xib file, a controller implementation (ie, subclass of UIViewController) and its corresponding header. You’ll create the xib using IB and lay everthing out. Do nothing more and try to compile it – you’ll get an error about the view outlet not being set. What does that mean? Remember how we are using the controller to access the view to add it to the window? It’s looking for a member (of the controller class) called view, which needs to correspond to the UIView instance that is, in fact, the interface you’re presenting. How does that get set? Look back to line 50 of the implementation file – you’ll see the controller instantiated like this: [[ControllerName alloc] initWithNibName: @”NameFromIB” bundle: nil]; …the initWithNibName method of UIViewController loads the Interface Builder file and sets the controller’s view parameter based on the view outlet set through IB. Since no view outlet has been set in IB, you’re seeing the error.
-
OK, so how do I eliminate the error? Simple. In Interface Builder, set the File’s Owner property of the view (in the WhateverName.xib window) you’re trying to load to the name of the controller that should correspond to it (I’m guessing this seeming redundancy is so that IB can determine the variables/actions available and prevent errors before compiling). Next, click the second tab of the view’s property window (My View Connections) and drag the view outlet over to the File’s Owner item in the WhateverName.xib window. Done. (Note that my terminology may be off and I’m referring to Xcode 3 – can’t upgrade the work machine yet).
-
There’s been a lot of talk about UIView instances – why? The basic answer is because pretty much everything in your interface inherits from UIView (or is one directly). Look around the apple class reference docs and you’ll see it in most inheritance hierarchies: UITableView, UIButton, UINavigationBar, etc. Astute readers will notice that components that contain UIViews are, nevertheless, UIViews themselves. UIView instances can establish a container relationship by calling [uiviewInst addSubview: anotherInst]; It’s well worth reading through Apple’s docs on this class – you’ll never build a significant app without it.
-
I see initWithCoder in some code – what’s that? For the vast majority of cases, you’ll use initWithNibName, not initWithCoder. It’s basically called to deserialize child components from within Interface Builder files. A better answer can be found here.
-
Great – I’ve got an interface, it compiles and displays… now how do I talk to my controller? There are two ways you’ll want to interface with the controller from IB: configuring variable and assigning methods to handle events. To examine both, take a look at the header and implementation for EcoItemsViewController. On line 13 of the header, you’ll notice the keyword IBOutlet before a member variable definition. IBOutlet marks a variable as available for setting via IB. In almost all cases, these will be UI component variables, such as the UITableView seen here. In IB, the association is completed by accessing the View Connections for the UI component and dragging the New Referencing Outlet property to File’s Owner. You’ll then see a popup menu of possible variables based on type (hopefully including whatever you created in the header) – select the correct variable and you’re golden. Note that you’ll most often see @property definitions for IBOutlets, along with an @synthesize call. Why is this? Take a look here. For methods, the process is pretty similar: instead of defining your event handler as being type (void), you’ll set it to (IBAction). Your method is now similarly available in IB. Click on whatever UI component will be responsible for your event (eg, a UIButton) and find the event in the same View Connections list. For a UIButton, the correct event is often Touch Up Inside. Drag the event to File’s Owner and you’ll see a list of available IBActions – same process as above.
-
How can I navigate between views? There are canned ways, such as having IB create a tab bar application for you, but I’ll talk about the most flexible way: UINavigationController. Make an instance of this as your first displayed view, but remove the actual visibility of the navigation bar (check its hidden property in IB). Now, in your applicationDidFinishLaunching: method in the AppDelegate, immediately cover that up with the actual first view you’d like to show. To cover it up, do this: [navigationControllerinstance pushViewController: viewInstance animated: YES] (obviously adjust the animated parameter according to your needs). You can call this anywhere in the app, as long as you’ve got a reference to the navigation controller, and push a view to the top. So how will you have an instance of navigation controller from within a pushed page? Simple. self.navigationController is set when a view is pushed, so you should never be without an instance. When you’re done with a view, you can call the popViewControllerAnimated: method: [self.navigationController popViewControllerAnimated:YES]; …This means of navigation is pretty much flexible enough to handle anything you want. Note that this is NOT the way to handle modal windows – check the docs for Modal View Controllers.
-
A word of caution – iOS UI programming can be frustrating. Apple likes their interfaces to conform to a standard and sometimes it’s difficult to programmatically sidestep their decisions.
Some Odds and Ends:
These are important topics that don’t really fit anywhere else. They’ll tend to come up in more complex apps and are well worth understanding in advance.
-
So how can I share data between controllers? OK – you’ve tried passing things around, but clearly that won’t work in methods implementing protocols. The next thing you try is sticking things in your AppDelegate and accessing them through a reference, but although that works, it makes you feel like you need to take a shower. Singletons are your shower. The singleton pattern is basically a way to ensure that there’s only ever one instance of an object. Just remember that your shower doesn’t have any soap – you’re still effectively using globals, but it’s better than nothing. I’ll leave you to visit this link for the details on implementation.
-
I’m updating a UI component but I never see the update – wtf? This one bit me at the end of last week with no time left to solve it (I’ll update when it’s solved). The reason you’re experiencing this is that the update is taking place from a background thread. You need to force the update to run in the main thread. Here’s a StackOverflow question that answers how to do this.
Great – if you’ve followed along, you’re now minimally dangerous with Objective C. Feel free to update me if you learn anything particularly interesting.