As a courtesy, this is a full free rendering of my book, Programming iOS 6, by Matt Neuburg. Copyright 2013 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 13 Programming Fundamentals with Swift and Programming iOS 13. If my work has been of help to you, please consider purchasing one or both of them, or you can reward me through PayPal at http://www.paypal.me/mattneub. Thank you!
A nib file, or simply nib, is a file containing a drawing of a piece of your interface. The term nib is not really an English word (it has nothing to do with fountain pens or bits of chocolate); it is based on the file extension .nib, which originated as an acronym for “NeXTStep Interface Builder”. Nowadays, you will usually develop your interface using a file format whose extension is .xib; when your app is built, your target’s .xib files are translated (“compiled”) into .nib format (Chapter 6). But a .xib file is still referred to as a nib file. I will speak of the same nib file as having either a .xib extension (if you’re editing it) or a .nib extension (if it’s in the built app).
You construct your program in two ways — writing code, and drawing the interface. But these are really two ways of accomplishing the same ends; drawing the interface is a way of writing code. When the app runs and your drawing of the interface in a nib file is loaded, it is translated into instructions for instantiating and initializing the objects in the nib file. You could equally have instantiated and initialized those same objects in code. (This point is crucial; see Nib-Based Instantiation.) Indeed, deciding whether to create an interface object in code or through a nib file is not always easy; each approach has its advantages. The important thing is to understand how interface objects drawn in a nib file are instantiated and connected to your code when the app runs. That’s the subject of this chapter.
(Most of this chapter applies equally to storyboards. Don’t skip this chapter on the grounds that you intend to use storyboards instead of nibs! Storyboards do not relieve you of the need to understand nib management. I’ll discuss the particular nature and use of storyboards in Chapter 19.)
Up through Xcode 3.2.x, nib editing was performed in a separate application, Interface Builder. Starting in Xcode 4, the functionality of Interface Builder was rolled into Xcode itself. Nevertheless, the Xcode interface for nib editing is still referred to as Interface Builder.
Let’s use an actual nib file to explore the Xcode nib-editing interface. In Chapter 6, we created a simple Xcode project, Empty Window; it contains a nib file, so we’ll use that. In Xcode, open the Empty Window project, locate the ViewController.xib listing in the Project navigator, and click it to edit it.
Figure 7.1 shows the project window after selecting ViewController.xib and making some additional adjustments. The Navigator pane is hidden; the Utilities pane is showing. Within the Utilities pane, the Size inspector and the Object library are showing. The interface may be considered in four pieces:
The collapsed dock, as I’ve already said, shows the nib’s top-level objects. To see what this means, you need first to envision the nib as containing objects. Some of these objects — those that represent views — are arranged in a hierarchy of containment. Objects that are contained by no other object are top-level objects.
A view can contain other views (its subviews) and can be contained by another view (its superview); for example, if we were to put a button into the view that occupies the canvas in Figure 7.1, the button would be a subview of that view, and that view would be the button’s superview. One view can contain many subviews, which might themselves contain subviews. But each view can have only one immediate superview. Thus there is a hierarchical tree of subviews contained by their superviews with a single object at the top. The highest superview of any such hierarchy in the nib is a top-level object and appears in the dock. That’s why the view object (labeled View in Figure 7.1) appears in this nib’s dock: it is a view contained by no other view.
A top-level object, then, is either a view without a superview or else not a view at all. A nib file can actually portray two types of top-level object:
The dock can be expanded (by clicking the right-pointing triangle-in-a-square-in-a-circle at its lower right); it then portrays objects by name, and shows as an outline the full hierarchy of objects in this nib (Figure 7.2). At present, expanding the dock may seem silly, because there is no hierarchy; all objects in this nib are top-level objects. But when a nib contains many levels of hierarchically arranged objects (plus, starting in iOS 6, their autolayout constraints — see Chapter 14), you’re going to be very glad of the ability to survey them all in a nice outline, and to select the one you’re after, thanks to the expanded dock. You can also rearrange the hierarchy here; for example, if you’ve made an object a subview of the wrong view, you can reposition it within this outline by dragging its name.
You can also select objects using the jump bar at the top of the editor. For example, if you click on the canvas background so that no object is selected, and the rightmost jump bar path component reads No Selection, then the entire hierarchy of objects in your nib is shown as a set of hierarchical menus off that path component (Control-6). Again, this may seem like small potatoes now, when your nib contains just three top-level objects and nothing more, but it will be valuable when you’ve many nib objects in a hierarchy.
The name by which a nib object is designated is its label. This name is meaningful to the nib file, not to your code. When the dock is expanded, each object is portrayed by its label, as shown in Figure 7.2. When the dock is collapsed, you can see a top-level object’s label by hovering the mouse over it, as shown in Figure 7.1. If you find an object’s label unhelpful, you can change it: select the object and edit the Label field in the Document section of the Identity inspector (Command-Option-3). Alternatively, select the object’s label in the expanded dock and press Return to make it editable.
The canvas provides a graphical representation of a top-level nib object along with its subviews, similar to what you’re probably accustomed to in any drawing program. If a top-level nib object has a graphical representation (not every top-level nib object has one), you can click on it in the dock to display that representation in the canvas.
To remove the canvas representation of a top-level nib object, click the X at its upper left; this merely clears the representation from the canvas — it does not remove the top-level nib object from the dock (or from the nib), and of course you can always bring back the graphical representation by clicking that nib object in the dock again. On the other hand, the canvas is scrollable and automatically accommodates however many graphical representations it contains, so, rather than removing and restoring graphical representations, you can keep many graphical representations open in the canvas and scroll to see each one.
Our simple Empty Window project’s ViewController.xib contains just one top-level nib object that has a graphical representation — the UIView destined to be the root view of the app’s window, called View. The term “root” here implies that the view occupies the entire window. Because this view will be the root view of our app’s window, any changes you make here will be reflected in the app’s user interface when you run it. To see this, we’re going to add a subview to it:
A button now appears in the view in the canvas. The move we’ve just performed — dragging from the Object library into the canvas — is extremely characteristic; you’ll do it often as you design your interface. Alternatively, select an object in the Object library and press Return to copy the object into your interface (you might first want to make sure the correct superview is selected in the canvas). Filtering by name with the filter bar also selects, and switching to the Object library with Control-Option-Command-3 also puts focus in the filter bar, so a rapid keyboard-only way to add a button to your interface is to type Control-Option-Command-3, “button”, possibly Up or Down arrow to select Round Rect Button, Return.
Take a moment to play around with the button in the view in the canvas. Much as in a drawing program, the nib editor provides features to aid you in designing your interface. Here are some things to try:
(In Xcode 4.5 and later, you’ll also see what look like guidelines or dimension bars when the button is selected and at rest. Those are autolayout constraints, to be discussed in Chapter 14.)
Let’s prove that we really are designing our app’s interface. We’ll run the app to see that its interface has changed.
After a heart-stopping pause, the iOS Simulator opens, and presto, our empty window is empty no longer (Figure 7.4); it contains a round rect button! You can tap this button with the mouse, emulating what the user would do with a finger; the button highlights as you tap it.
There are four inspectors that appear only when you’re editing a nib and apply to whatever object is selected in the dock or canvas:
Settings here correspond to properties and methods that you might use to configure the object in code.
For example, changing the setting in the Background pop-up menu in the Attributes inspector for our view corresponds to setting the backgroundColor
property for the view in code. Similarly, typing a value in the Title field in the Attributes inspector for our button is like calling the button’s setTitle:forState:
method.
The Attributes inspector has sections corresponding to the selected object’s class inheritance. For example, the UIButton Attributes inspector has three sections, because a UIButton is also a UIControl (“Control” in the inspector) and a UIView (“View” in the inspector).
The correspondence between Attributes inspector settings and Objective-C methods is mostly a matter of guesswork. The Attributes inspector doesn’t always tell you, and there’s no way to see the code generated when the nib actually loads.
The X, Y, Width, and Height fields determine the object’s frame (its position and size within its superview), corresponding to its frame
property in code; you can equally do this in the canvas by dragging and resizing, but numeric precision can be desirable.
What else you see in the Size inspector depends on whether this nib is using autolayout or not. Autolayout, a new iOS 6 feature, may be toggled on or off for a nib at file level: select the nib file in the Project navigator, show the File inspector (Command-Option-1), and check or uncheck Use Autolayout. The default for new nibs in Xcode 4.5 and later, and thus for our Empty Window project, is that autolayout is on. The possibilities for what you’ll see in the rest of the Size inspector are:
autoresizingMask
property, determining how the object will be repositioned and resized when its superview is resized; a delightful animation demonstrates visually the implications of your settings. The Arrange pop-up menu contains useful commands for positioning the selected object.
There are two libraries that are of particular importance when you’re editing a nib:
A nib file is useless until your app runs and the nib file is loaded. If a nib is designated by the Info.plist key “Main nib file base name” (NSMainNibFile
, see Chapter 6), it is loaded automatically as the app launches; but this is an exceptional case, and has now fallen out of favor — there are no automatically loaded main nib files in the current project templates. In general, nibs are loaded explicitly as needed while the app runs.
In our Empty Window application, you can actually see where this happens, in AppDelegate.m:
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
That line of code does several things, one of which is that (for reasons to be explained more fully in Chapter 19) it causes the nib named @"ViewController"
(i.e., the nib file compiled from ViewController.xib, the nib file we’ve been editing) to be loaded, and the resulting views to be put into our app’s interface — which is how we were able to obtain the outcome shown in Figure 7.4.
So a nib is not loaded until the app runs and our code decides, at some point in the life of the app, that that nib is needed. This architecture is a source of great efficiency. For example, imagine our app has two complete sets of interface, and the user might never ask to see the second one. It makes obvious sense not to load a nib containing the second set of interface until the user does ask to see it. By this strategy, a nib is loaded when its instances are needed, and those instances are destroyed when they are no longer needed. Thus memory usage is kept to a minimum, which is important because memory is at a premium in a mobile device. Also, loading a nib takes time, so loading fewer nibs at launch time makes launching faster.
In order to use nibs (or storyboards, since a storyboard is like a collection of multiple nibs), it is crucial that you understand what happens when a nib loads.
When a nib loads, some already existing instance is designated its owner. A nib cannot load without an owner, and the owner must exist before the nib can load. The nib owner can be an instance of any class; it will often, though not necessarily, be a UIViewController instance, because a UIViewController already knows how to load a nib and manage a view that it contains (Chapter 19).
The three most common ways of telling a nib to load are:
initWithNibName:bundle:
loadNibNamed:owner:options:
[NSBundle mainBundle]
. Observe that the owner is one of the parameters. I’ll show an example in the next section of this chapter.
instantiateWithOwner:options:
When a nib loads, its nib objects are instantiated, meaning its top-level nib objects and all deeper-level nib objects hierarchically dependent on them. (Proxy objects, by definition, exist before the nib loads; nib loading does not instantiate them.) For example, in our nib, the view is instantiated when the nib loads, bringing with it the button inside it. (Again, see Nib-Based Instantiation; make very sure you understand this point!) This is what nibs are for — to instantiate objects when they load. To put it another way, that is what nib loading is — it is the instantiation of the nib objects described in the nib. At that point, having loaded, the nib’s work is done; the nib does not, for example, have to be “unloaded.”
The same nib can be loaded multiple times, generating an entirely new set of instances each time. A frequent beginner question is, “I have a view in a nib; how do I make multiple copies of this view?” The simple solution is to load that nib multiple times. This is common practice. For example, consider table view cells. Every “row” of a table view is a table view cell. Let’s say there’s a certain look and behavior you want each “row” to have. You design the cell in a nib of its own as a UITableViewCell. If the table has to display ten rows, you load that nib ten times (Chapter 21).
Let’s create our own nib-loading code, illustrating at the same time the fact that any instance can be a nib’s owner. To do so, we’ll need a second nib file in our project.
We’ve now created a nib file, MyNib.xib, containing a single top-level nib object, a UIView. Look at MyNib.xib in the editor to see that this is true.
We’ll also need an instance to act as the nib’s owner. By the time our code will run, we will already have at least one instance we could use (the AppDelegate instance), but to illustrate the procedure fully, we’ll create our own class whose sole purpose is to be instantiated so that this instance can act as the owner of the nib file as it loads:
We’ve now created files MyClass.h and MyClass.m declaring a class called MyClass.
Next, we’ll write code that will load our new nib when the app runs. We need a place in our little app where our code is guaranteed to run: we’ll use the AppDelegate instance method application:didFinishLaunchingWithOptions:
in the file AppDelegate.m. Just before or after the call to makeKeyAndVisible
, insert this code to instantiate MyClass and load MyNib.nib with that instance as its owner:
MyClass* mc = [MyClass new]; [[NSBundle mainBundle] loadNibNamed:@"MyNib" owner:mc options:nil];
Xcode will complain about this, because you can’t speak of MyClass without importing its declaration, so after the existing #import
at the start of this file, add this line:
#import "MyClass.h"
Now build and run the project. Our new MyNib.nib file loads, and its UIView top-level nib object is instantiated. Unfortunately, you can’t see that this is true! That’s because we haven’t done anything to obtain the UIView instance that came into existence from the nib loading; in fact, what really happened is that the UIView instance popped into existence and popped right back out again, like a virtual particle in quantum field theory. The next section corrects this, and shows how to obtain visible proof that our nib is loading and that its top-level nib objects are being instantiated.
You know how to load a nib file, thus instantiating its top-level nib objects. But those instances are useless to you if you don’t know how to get a reference to any of them in your code! Doing things with an object such as a label or a button or a text field or whatever (such as setting or getting the text it displays) is easy; but you have to be able to talk to the object in the first place, meaning that you need a reference to it, a variable that points to that instance (Chapter 3). Getting a reference to an instance that you created in code is trivial, because you assigned it to a variable at the time you created it (Chapter 5). But there’s no such assignment when you load a nib; you just load it and that’s the end of that:
[[NSBundle mainBundle] loadNibNamed:@"MyNib" owner:mc options:nil]; // no assignment??!! dude, where are my nib-created instances?
To refer in code to instances generated from nib objects when the nib loads, you need to have previously set up an outlet connection from a proxy object in the same nib.
A connection is a named unidirectional linkage from one object in a nib file (the connection’s source) to another object in the same nib file (the connection’s target). An outlet is a connection whose name corresponds to an instance variable in the source object. When the nib loads, and the target object is instantiated, the nib-loading mechanism assigns the value of the instance variable to be the target object. Thus the source object winds up with a reference to the target object as the value of one of its instance variables.
Connections can link any two objects in a nib file, but a proxy object as the source of a connection is special because it represents an object that exists before the nib loads. Thus an outlet from a proxy object causes an object that exists before the nib loads to end up with a reference to an object that doesn’t exist until after the nib loads — an object that is in fact instantiated by the loading of the nib.
In the most typical configuration, the proxy object will be the File’s Owner. The idea is that the instance that owns the nib has an instance variable, and the File’s Owner in the nib has a corresponding outlet to a nib object; the nib loads, and the owner instance ends up with an instance variable that refers to the instance generated from the nib object (Figure 7.5).
The File’s Owner top-level object in a nib file is a proxy for the instance that will be the nib’s owner when the nib loads. To form a successful connection between this proxy object and a nib object, the nib needs to know the class of that instance. In the nib editor, you must set the File’s Owner’s class to match the nib owner’s class. Obviously this means that you must know in advance, while editing the nib, what the class of the nib owner will be when the nib loads. But you do know this, because you (i.e. your code) is what’s going to load the nib.
To demonstrate the use of an outlet connection, we’ll implement exactly the schema illustrated in Figure 7.5, by making an outlet from the File’s Owner to a nib object in MyNib.xib.
The nib owner is going to be a MyClass instance when the nib loads. As I’ve just said, we must tell the nib that this is the case. Let’s do that first:
Next, we need a nib object in MyNib.xib to make an outlet to. We already have one — the nib’s existing top-level UIView. But this lacks visual impact, so we’ll replace the UIView with a top-level UILabel, which will draw some text:
Now comes the really crucial part. We need two things, in two different places:
When the app runs and MyNib.nib is loaded with a MyClass instance as its owner, as we arranged in the code we’ve already written, those two pairs of things will be effectively equated:
I’m simplifying. It isn’t really the identity of an instance variable’s name with that of the outlet that makes the match. It’s more complicated than that; the match is made using key–value coding. The rigorous details appear in Chapter 12.
You thus need to work in two places at once: the nib, and MyClass’s code. Before Xcode 4, this required working separately in two different places, Xcode (where the code was edited) and Interface Builder (where the nib was edited). Nowadays, Xcode itself edits both the code and the nib, and furthermore you can see the code and the nib at the same time, all of which will make creating this pair of things, the instance variable and the outlet, much easier than it once was.
I want you now to arrange to see two things at once: MyClass.m (the MyClass implementation file, where we’ll declare the instance variable) and MyNib.xib (where we’ll create the outlet). You could use two project windows if you wanted, but for simplicity, let’s use an assistant: while editing MyNib.xib, switch to Assistant view (View → Assistant Editor → Show Assistant Editor) as in Figure 7.6. If, when you showed the assistant pane, it didn’t appear with MyClass’s implementation file showing, use the jump bar in the assistant pane to make the assistant pane show MyClass.m: for example, in the first path component choose Manual → Top Level Objects → MyClass.m. If the assistant pane is beside the nib editor pane rather than below it, choose View → Assistant Editor → All Editors Stacked Vertically.
In MyClass.m in the assistant pane, at the start of the implementation section, create curly braces and declare a UILabel instance variable (and save):
@implementation MyClass { IBOutlet UILabel* theLabel; } @end
The term IBOutlet
is linguistically meaningless; it is #define
d as an empty string, so it is deleted by the preprocessor before the compiler ever sees it. It’s purely a hint to Xcode to make it easy for you to create the outlet. Xcode responds by displaying an empty circle in the gutter to the left of the IBOutlet
line; this indicates that although we’re speaking of an outlet in our code, no corresponding outlet connection yet exists in a nib. We’ll fix that in a moment.
We have typed the instance variable as a UILabel*
, because we happen to know that this is the type of object that this instance variable will be pointing to; we could also use id
, or any superclass of UILabel. If we do not use one of these alternatives (id
, UILabel, or a superclass of UILabel), we will not be able to form the connection to a UILabel in the nib.
We have accomplished half our task: we’ve made the instance variable. Now we’re ready for the other half, namely, to make the outlet connection. There are several ways to do this, so I’ll just pick one for now and demonstrate the others later:
theLabel
, is listed here! This is the work of the IBOutlet
hint we created earlier.
theLabel
in the Connections inspector, drag to the Label object in the canvas (Figure 7.7), and release the mouse. (A kind of elastic line follows the mouse as you drag from the circle to show that you’re creating a connection.)
With the File’s Owner object selected, look again at the Connections inspector; it shows that theLabel
is connected to the Label nib object, and if you hover the mouse over the filled circle at the right, the label object in the nib is highlighted. And look at the IBOutlet
line in MyClass.m; the circle in the gutter is now filled in, and if you click that filled circle, the label is specified in a pop-up menu next to the circle, and the label object in the nib is highlighted. Mission accomplished! We have made an outlet connection in the nib from the File’s Owner proxy (representing a MyClass instance) to the Label object, and this outlet connection has the same name as the instance variable theLabel
in MyClass’s code.
Therefore, when the nib loads and a MyClass instance is the nib’s owner, its theLabel
instance variable will be set to the UILabel object that will be instantiated through the loading of the nib. To prove that this is indeed the case, we’ll use that instance variable in our code to do something dramatic. In particular, we’ll stick the UILabel into our window, thus making it visible. Its visibility will prove that the nib is loading and that the instance variable is being set by the outlet.
Return to AppDelegate.m and modify the nib-loading code like this (you added the first two lines earlier):
MyClass* mc = [MyClass new]; [[NSBundle mainBundle] loadNibNamed:@"MyNib" owner:mc options:nil]; UILabel* lab = [mc valueForKey: @"theLabel"]; [self.window.rootViewController.view addSubview: lab]; lab.center = CGPointMake(100,100); lab.frame = CGRectIntegral(lab.frame);
(We haven’t written an accessor method in MyClass for theLabel
, so to save time I used key–value coding.) Build and run the app. The words “Hello, world!” appear! This proves that our outlet worked. We loaded a nib and, using an outlet, we obtained a reference to a nib object and were able to manipulate that object, putting it into our interface.
Making an instance variable and giving it an IBOutlet
hint, but forgetting to connect the outlet to anything in the nib, is an unbelievably common beginner (and not-so-beginner) mistake. Had we made this mistake, our code would have run without error, but “Hello, world!” would not appear in the interface because lab
would be nil. The unfilled circle that appears in the gutter next to an IBOutlet
line for which no corresponding nib connection exists is your only clue that something’s amiss, so watch for it.
I said a moment ago that there were other ways to create the outlet. Let’s try some of them. Return to our assistant-paned nib editor, select the File’s Owner, switch to the Connections inspector, and delete the outlet by clicking the little X to its left. We’re going to make this outlet again, a different way:
theLabel
as a possibility (Figure 7.8). Click theLabel
.
Once again, look at the Connections inspector with the File’s Owner selected to confirm that this worked. You can even build and run the project again, to prove it to yourself if you’re in any doubt. Now delete the outlet again; we’re going to make this outlet in yet a different way:
theLabel
to the label (Figure 7.9). Release the mouse.
Now delete the outlet again; we’re going to make this outlet in another way. This time, we’re going to operate from the point of view of the label. The Connections inspector shows all connections emanating from the selected object; it also shows all connections linking to the selected object. So, select the label and look at the Connections inspector. It lists “New Referencing Outlet.” This means an outlet from something else to the thing we’re inspecting, the label. So:
theLabel
appears. Click it.
Confirm that, once again, we’ve made an outlet from the File’s Owner to the label. (And we could also have done the same thing by Control-clicking the label to start with, to show its Connections HUD.) Now delete the outlet again; we’re going to make this outlet in another way. This time, we’re going to start with the label, but (hold onto your hat) we’re going to connect directly to the code which is sitting in the assistant pane:
IBOutlet
line declaring the instance variable theLabel
.
Yet again, confirm that we’ve successfully made the desired outlet. And you could also have done the same thing in reverse; starting with the circle at the left of the IBOutlet
line in the code, you can drag (without holding Control) to the label in the nib.
Now delete the outlet one last time, and (get this) delete the line of code declaring the instance variable (but leave the curly braces). We’re going to create the outlet and the instance variable declaration, all in a single amazing move:
theLabel
(and make sure the type is UILabel), and press Return. The IBOutlet
line declaring the instance variable is created, and the outlet is formed to match it.
All our examples so far have involved a proxy object, but an outlet connection can connect any two objects in the nib. The only requirement is that the source object be of a class that has an instance variable whose type matches the class of the target object. This class might be your own custom class with an ivar that you gave it, as in our earlier examples, or it might be a built-in Cocoa class with a built-in instance variable that can be used as an outlet.
Nothing in the documentation for a built-in Cocoa class tells you which of its instance variables are available as outlets. In general, the only way to learn what outlets a built-in class provides is to examine a representative of that class in a nib.
It is also possible to create an outlet collection. This is an NSArray instance variable matched by multiple connections to objects of the same type. For example, suppose a class contains this instance variable declaration:
IBOutletCollection(UILabel) NSArray* labels;
Then it is possible to form multiple labels
outlets from an instance of that class in a nib, each one to a different UILabel in that nib. When the nib loads, those UILabel instances become the elements of the NSArray labels
. The order in which the outlets are formed is the order of the elements in the array. This turns out to be particularly useful when forming outlets to autolayout constraints; examples will appear in Chapter 20 and Chapter 23.
Having explored outlet connections, we should also discuss the other kind of connection, action connections.
An action is a message emitted automatically by a Cocoa UIControl interface object (a control) when the user does something to it, such as tapping the control. The various user behaviors that will cause a control to emit an action message are called events. To see a list of possible events, look at the UIControl class documentation, under “Control Events.” For example, in the case of a UIButton, the user tapping the button corresponds to the UIControlEventTouchUpInside
event. In the case of a UITextField, the user typing or deleting or cutting or pasting corresponds to the UIControlEventEditingChanged
event. A complete list of UIControls and what events they report is provided in Chapter 11, and each type of control is thoroughly dealt with in later chapters, especially Chapter 25.
An action message, then, is a way for your code to respond when the user does something to a control in the interface, such as tapping a button. But your code will not receive an action message from a control unless you explicitly make prior arrangements with that control. You must tell the control what event should trigger an action message, what instance to send the action message to, and what the action message’s name should be. There are two ways to make this arrangement: in code, or in a nib.
Either way, we’re going to need a method for the action message to call. There are three standard signatures for a method that is to be called through an action message; the most commonly used one takes a single parameter, which will be a reference to the object that emitted the action message. (For full details, see Chapter 11.) So, for example, you could have a method like this (let’s agree to put it in the implementation section for ViewController, in ViewController.m):
- (void) buttonPressed: (id) sender { UIAlertView* av = [[UIAlertView alloc] initWithTitle:@"Howdy!" message:@"You tapped me." delegate:nil cancelButtonTitle:@"Cool" otherButtonTitles:nil]; [av show]; }
Now, as I mentioned a moment ago, it is possible to arrange in code for buttonPressed:
to be called when the user taps a button. In particular, if b
is a reference to the button, then some ViewController code could say:
[b addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
That code means: “Hey there, button! When the user taps on you (UIControlEventTouchUpInside
), send me (self
) a buttonPressed:
message.” (See Chapter 3 if you’ve forgotten about the @selector
directive.) Of course, such an instruction assumes that this object (self
) really does implement a buttonPressed:
method. (If it doesn’t, then when the user taps the button, the app will crash.)
However, instead of doing that, we’re going to use the existing button in ViewController.xib and arrange in the nib for its action message to be buttonPressed:
and to be sent to a ViewController instance. We’re going to form an action connection in the nib. We can do this because, as I’ve already mentioned, a ViewController instance is the owner of ViewController.nib when it loads. The template has already set the File’s Owner proxy in ViewController.xib to be of the ViewController class (look at it and see!). So we can create an action connection in the nib from the button to the File’s Owner, as a way of communicating between the button and the ViewController instance that loads the nib when the app runs.
As with outlets, there are several ways to do this; I’ll just show you the main ones and leave you to discover the rest. (They are all directly comparable to the many ways of creating an outlet connection.)
We need a hint, in our code, that a method with the expected signature exists. This hint involves substituting IBAction
for the method’s void
return type. (The substitution is legal because IBAction
is #define
d as void
; Xcode can see the hint in your code, but the preprocessor will turn IBAction
back to void
before the compiler ever sees it.) So, in ViewController.m, change the first line of our buttonPressed:
method implementation to look like this (and save the file):
- (IBAction) buttonPressed: (id) sender {
This causes an empty circle to appear in the gutter next to the IBAction
line.
buttonPressed:
. Click on buttonPressed:
to form the connection.
To see that the action connection has been formed, look at the Connections inspector. If you select the button, the Connections inspector reports that the button’s Touch Up Inside event is connected to the File’s Owner’s buttonPressed:
method. If you select the File’s Owner object, the Connections inspector reports a Received Action where buttonPressed:
is called by the Rounded Rect Button’s Touch Up Inside event. Finally, look at the code in ViewController.m; the circle next to the IBAction
line is filled, and you can click it to reveal that the connection is from the button.
Finally, to make assurance doubly sure, you can also build and run the project to confirm that the action connection is working. In the running app, the button inside the window now actually does something when the user taps it! It summons an alert.
As with outlets, we could have formed the action connection by Control-dragging from the button directly to the File’s Owner, instead of involving the Connections inspector. If you just Control-drag, Interface Builder assumes a default event for you (in this case, it would assume Touch Up Inside). If that isn’t what you want, start by Control-clicking on the button to summon a HUD version of the Connections inspector, and drag from the desired event’s circle just as you would do from the real Connections inspector.
As with outlets, you can also form the action connection directly from nib to code. (But please reread Connecting to Code is an Illusion; that warning applies equally to action connections.) In Figure 7.11, we’ve Control-clicked the button in the nib to summon its Connections HUD, and dragged from the Touch Up Inside circle to the buttonPressed:
implementation in code. And we could equally have gone the other way, dragging from the unfilled circle next to the IBAction
line in the code to the button in the nib.
But wait, there’s more! Instead of writing the action method ahead of time, you can ask Xcode to stub it out for you. To do so, Control-drag from the nib to an empty spot in ViewController’s implementation section; the words Insert Action appear, and when you release the mouse, a dialog appears, letting you specify the name of the action method, the number of arguments it should take, and the control event to be used as a trigger. Xcode inserts the method implementation, but doesn’t put any code between the curly braces; it’s smart, but not smart enough to guess what you want the method to do!
By the time a nib finishes loading, its instances are fully fledged; they have been initialized and configured with all the attributes dictated through the Attributes and Size inspectors, and their outlets have been used to set the values of the corresponding instance variables. Nevertheless, you might want to append your own code to the initialization process as an object is instantiated from a loading nib. Most commonly, to do this, you’ll implement awakeFromNib
(possibly subclassing a Cocoa class in order to do so). The awakeFromNib
message is sent to all nib-instantiated objects just after they are instantiated by the loading of the nib: at the point where this happens, the object has been initialized and configured and its connections are operational.
For example, our Empty Window app is loading MyNib.xib, extracting a UILabel from it, and inserting that label into our interface; the result is that the words “Hello, world!” appear in our window. Let’s modify the behavior of this UILabel so that it does some additional self-initialization in code. To do that, we will need a class of our own to which our UILabel will belong. Clearly, this needs to be a UILabel subclass. So:
In MyLabel.m, somewhere in the implementation section, implement awakeFromNib
:
- (void) awakeFromNib { [super awakeFromNib]; self.text = @"I initialized myself!"; [self sizeToFit]; }
Now build and run the project. Instead of “Hello, world!” we now see “I initialized myself!” in the window.
If you’re an experienced Mac OS X programmer, you may be accustomed to rarely or never calling super
from awakeFromNib
; doing so used to raise an exception, in fact. In iOS, you must always call super
in awakeFromNib
. Another major difference is that in Mac OS X, a nib owner’s awakeFromNib
is called when the nib loads, so it’s possible for an object to be sent awakeFromNib
multiple times; in iOS, awakeFromNib
is sent to an object only when that object is itself instantiated from a nib, so it can be sent to an object a maximum of once.
Sometimes, you might need to interfere with a nib object’s initialization at an even earlier stage. If this object is a UIView or UIViewController (or a subclass of either), you can implement initWithCoder:
. In your implementation, be sure to call super
and return self
as you would do in any initializer. Your purpose here would typically be to initialize additional instance variables that your subclass has declared, as with any initializer.
Here, for example, is an implementation of MyLabel that declares an instance variable that is an int called _num
and manipulates it first in initWithCoder:
and then in awakeFromNib
, thus proving that the two are called in that order:
@implementation MyLabel { int _num; } - (id) initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { self->_num = 42; } return self; } - (void) awakeFromNib { [super awakeFromNib]; self.text = [NSString stringWithFormat: @"The answer is %i", self->_num]; [self sizeToFit]; } @end
That’s trivial and unnecessary, but it illustrates the principle.