iApps Development Blog
iApps Development Blog
How I Grew to Love Interface Builder
When I first came to iPhone SDK programming, the whole Cocoa programming thing was very new to me. I had not used Xcode to create any Mac apps, so my learning curve — which included going all the way back to learning the fundamentals of C — was pretty steep. For a long time, I felt I was trying to take sips from a gushing fire hose.
In the process of learning all this stuff, I got my hands on every iPhone programming tutorial I could find. I also dove into Cocoa programming books (even if they taught me some things that didn't apply to the iPhone SDK). I knew that if I read enough stuff often enough, pored over sample code enough times, and tapped out some simple demo apps that things would start to fall into place in my head. That did happen except for one stumbling block: Interface Builder (IB).
The more tutorials I followed, the more confused I got about Interface Builder. There were plenty of things I grokked right away. I understood the roles of outlets and actions. And laying out UI elements was just plain fun. But when it came time to blending the work done in IB with the code in Xcode, a mental block developed faster than a brick wall in the last seconds of a Tetris game.
Although Apple supplied some sample iPhone apps in the SDK that used IB, I hungered for more explanations of why the programmer structured his .xib file the way he did and, more importantly, in what sequence. I think it’s easy to mistake Apple sample code as having been carved by lightning bolts on Mt. Sinai (or Horeb, as the case may be), representing THE WAY to do things. Without further explanations or reasoning behind the decisions, the code student is left to his or her own rationalizations as to why things were done the way they were. That can be dangerous.
I didn’t recognize it at the time, but the problem I was having with IB was that almost every tutorial had a different, if not inconsistent, way of incorporating IB into the development workflow. Some tutorials suggested that the way to do things is to design your layout in IB first, create identifiers for outlets and actions there, and then either have IB generate the corresponding Xcode class file or manually introduce your outlets and actions to your existing class files. [This is how Apple engineer Evan Doll demonstrated the iPhone SDK to the recent Stanford iPhone Application Development course, and then (in an admittedly hurried first-lecture demo) managed to mess things up.] Other authors went the other way: Defining outlets and actions in the code first, and then jumping to IB to lay them out and make the desired connections. If those two conflicting strategies weren’t enough to confuse me, trying to figure out when to Control-drag from this to that in IB to make connections just sent my head spinning. I think there were at least three times when I literally wrote out a cheat sheet of the steps to follow — only to find myself lost the next time I tried to follow those steps in a demo project.
Along the way up my learning curve, I got a little sidetracked — but comfortably so — by Erica Sadun's book, The iPhone Developer's Cookbook (Addison-Wesley). I knew at the time that Erica had the unenviable task (suffered by yours truly many times in the past) of composing a fixed book against a moving target. In this case, it was the ever-changing iPhone SDK beta, whose evolution was fairly rapid. At least in the early PDF version of the book that I was reading, Interface Builder was not a high priority. Some of this may have been due to Erica’s pioneering work diving into iPhone programming before the official release of the SDK (and Interface Builder). She was probably accustomed to laying out elements in views by defining CGRect frames for their views and adding them as subviews.
Given my IB confusion, this reliance on code that you can see and touch was comforting. Sure, tweaking pixel-perfect sizes and placements meant lots of building and running, but I understood what each statement did and could even single-step through a sequence in the debugger for more reassurance. You lose that code-level comfort when layout is done in IB — although IB generates code somewhere, you can’t see it or edit it anywhere (that I know of). In the end, I stuck with hand-coded user interfaces for the first release of iFeltThat.
My first app may have been en route to the App Store, but that didn’t stop me from wanting to learn more. That new programmer smell had not yet worn off. One of the iPhone developer blogs I follow posted a notice that book publisher Apress had a one-day introductory price on the PDF prerelease of Beginning iPhone Development by Jeff LaMarche and my friend Dave Mark. I jumped on it and was rewarded by gaining a jetpack for my climb up the curve.
Not only did this book assume one would be working with IB from the start, but it consistently promoted a workflow that blended well with the way I liked to work on iPhone apps. As I worked through the book, more and more IB lights went on in my head. I started to practice what I had learned as I got started on my second app, BeaconAid-HF. Unlike the first round of iFeltThat, BeaconAid-HF was built with IB for just about every view, including a custom table cell, following an example in the book.
Here, then, is the typical workflow I follow for designing a typical large view (assuming you have already created the Xcode project and the app delegate class is in place):
1. Sketch out views by hand on pencil and paper until a workable UI design is ready (I believe this should be done before you even choose New Project in Xcode, but that’s just me).
2. On the sketch, assign identifiers to elements that will be outlets (i.e., those whose content or settings will be controlled dynamically by code).
3. Make a list of methods that you expect to be actions resulting from user interaction with UI elements.
4. In Xcode, create a new class file (such as a UIViewController type for a main view). I name such classes as [DescriptiveName]ViewController, where [DescriptiveName] is replaced by a name that describes the view's role in the application.
5. Define the IBOutlet objects and IBAction methods (even if the methods are empty for now) in the code in the interface. I also follow the Mark/LaMarche book convention of defining IBOutlets as properties, which then must be synthesized and released in the implementation code.
6. Still in Xcode, click on the Resources folder and create a new file of the User Interface type (select the View type for a main view). I name the .xib file so that it represents the view of the controller where the outlets and actions are defined: [DescriptiveName]View.xib.
7. In the .xib window that shows the File’s Owner and other icons, select the File’s Owner.
8. Type Cmd-4 to view the Identity Inspector for the File’s Owner. In the top field (Class Identity), pull down the list and find the name of the view controller class for which this view is being defined (i.e., where your IBOutlets and IBActions definitions are located). After you make your selection, the names of your outlets and actions should appear in the areas below based on the definitions you had placed in the view controller’s interface file (Step 5).
Associating this .xib with a view controller class (i.e., the .xib file’s owner) is the most vital part of the process. This class identity tells Xcode to blend the .xib and controller together such that when the view controller object is instantiated at runtime, all of the elements laid out in the .xib file are also instantiated. In other words, the .xib file is taking the place of all the code that would have to be handwritten to 1) define CGRect frames, 2) alloc/init buttons, labels, sliders, etc., and 3) add them as subviews. With the underlying UIView and all its elements “automatically” instantiated, your view controller code can then address them to read their values or fill their content with dynamically-generated text, colors, etc.
[Part of the at-first mystery about IB is how closely it works with Xcode in realtime — something you might not expect from two separate applications. Once you’ve hooked up your File’s Owner identity to your view controller class, any changes you make to the Xcode interface file that impact IB (e.g., adding an outlet) are immediately reflected in your .xib file.]
9. Use the Library (Tools->Library) to drag elements to the View window. I prefer to expand the Library and Cocoa Touch Plugin groups so that I can limit my choices to, say, Inputs & Values. It makes it easier to find things.
10. If the view contains lots of elements, I tend to change the iconic view of the main .xib window to the outline style because I can see more element references quickly. (And if you double-click an item in the list, its element is selected in the View window to remind you which item it is — very helpful for things like initially empty labels and views.)
11. Now it’s connection time. Start by Control-dragging from the File's Owner to the View, and select “view”.
12. For each IBOutlet, Control-drag from the File’s Owner icon to either the outlet’s representation in the .xib window or the actual element in the View window; choose the IBOutlet identifier you had planned for that element. What you're doing here is telling Xcode to associate the object defined in code with the “physical” object you placed into the IB view — anytime your running code refers to that object in the code, it is referencing the UI element you dragged, positioned, and sized in the IB view. Note that for outlets, the dragging direction is FROM the File’s Owner icon to the outlet.
13. For elements that have action methods waiting for them, select each element and type Cmd-2 to see the Connections Inspector. Find the event type to which you want the element to respond, then drag from the empty circle to the File’s Owner icon in the .xib window; choose the IBAction you have already defined in the view controller class for this element’s action. Note that for actions, the dragging direction is TO the File's Owner icon.
14. Save the .xib file.
In looking through the above list, it looks like a lot of steps. But as you become accustomed to the sequence, you’ll think of it in fewer macro steps, namely:
1. From a good model (e.g., sketch) of your UI, define IBOutlet and IBAction items in the view controller code.
2. Create an associated .xib file that will specify user interface elements for the view.
3. Connect the File’s Owner Class Identity in IB.
4. Layout the elements in IB.
5. Make the connections in IB (don't forget to connect File’s Owner to the View).
(The sequence is a little different for defining a custom table cell in IB, which starts with an Empty .xib type — connections are made with the View, not the File’s Owner — but the principles are the same. The Mark/LaMarche book has an example.)
This structure means that I limit typing within IB to UI features, such as initial label text and precise dimensions. Any typing having to do with defining identifiers for UI elements is done exclusively in Xcode, and IB picks up those names for me to choose from when making connections.
I’m so sold on Interface Builder for standard UIKit designs that I have retrofitted parts of FeltThat to use it (starting with version 1.1, currently “in review”). The amount of source code has shrunk (although not necessarily the size of the binary), and the program will be easier to maintain and upgrade in future versions planned in the development roadmap for the product.
My way isn’t the one-and-only way to combine Xcode and Interface Builder, but it certainly works for me. Perhaps not all of IB’s mysteries have been solved, but my attitude toward it has changed from fear and confusion to a total embrace.
[Comments, suggestions, and corrections welcome via my iphone inbox at my domain, dannyg.com.]
Thursday, April 16, 2009