When your apps needs the objects stored in an Interface Builder file, it loads the interface. Figure 15-1
shows the Detail View scene of the Main_iPhone.storyboard file (from MyStuff in Chapter 7).
Figure 15-2 shows the (simplified) graph of objects contained in that scene.
Figure 15-1. Detail View in Interface Builder
Figure 15-2. Graph of objects in the Detail View scene
A storyboard scene consists of at least one top-level object, the view controller.
The view controller’s view property refers to its single root view object (UIView).
This, in turn, contains a collection of subviews (managed by anNSArray).
Some of those view objects refer to additional objects, such as NSString, UIImage, and
UIGestureRecognizer objects.
The -instantiateViewControllerWithIdentifer: method instigates the recreation (un-archiving) of the view controller, and all of its related objects, stored in the storyboard scene. This method is invoked automatically when triggered bya segue or, as you did in the Wonderland app (Chapter 12), you can send it programmatically to create a view controller when it pleases you.
During de-serialization (un-archiving), the property values and connections in the serialized data are used to instantiate new objects, set their properties, and connect them together.
Loading an .xib File
In the SunTouch project, you designed the game play interface in a separate .xib file.
When loading an interface builder file yourself, or letting UIViewController load it for you, the object relationships are subtly different.
Refer again to Figure 15-2. In the storyboard scene, there is only one top-level object (the view controller) and the entire graph of objects is reconstructed by un - archiving that single object.
Take a close look at the STGameViewController.xib file,
shown in Figure 15-3, and its simplified graph of objects, shown in Figure 15-4.
Figure 15-3. STGameViewController.xib in Interface Builder
Figure 15-4. Graph of objects in STGameViewController.xib
The difference is the placeholder object. Placeholder objects—the most important being the file’s owner—are objects that already exist when the Interface Builder file is loaded.
During the un-archiving process, existing objects aresubstituted for the placeholders.
The existing objects become part of the object graph, but are not created by, the Interface Builder file. Outlets in a placeholder can be set to objects created during the loading process, and objects in the graph can be connected to a placeholder. In Figure 15-4, the scoreLabel and weightLabel properties in the file’s owner (the view controller) are set to the twonew UILabel objects.
So far in this book, your use of Interface Builder files has been largely transparent. You either created interfaces in a storyboard scene or standalone .xib file, loaded automatically by its view controller. Now you’ll learn how to loadthem yourself and how to designate placeholder objects.
Placeholder Objects and the File’s Owner
When an Interface Builder file is loaded, the sender supplies the existing objects that will replace the placeholders in the file. The most common scenario is to use one placeholder object, referred to as the file’s owner.
This is typically the object loading the file; when a view controller loads its Interface Builder file, it declares itself as the file’s owner. You can provide any object you choose or none at all, in which case there are zero placeholder objects. Optionally, you can supply as many additional placeholder objects as you wish. (Later in this chapter you’ll load an Interface Builder file with multiple placeholders.) Think ofthe file’s owner as the “designated placeholder,” provided to make the common task of loading an Interface Builder file with one placeholder object as easy as possible.
The important rule to remember is that the class of the file’s owner in the Interface Builder file must agree withthe class of the owner object when the file is loaded. You set the class of the file’s owner using the identity inspectorin Interface Builder. When you set this, you’re making a promise that the actual object will be of that class (or a subclass) when the file is loaded.
Changing the class of the file’s owner from UIViewController to UIApplication won’t magically give your Interface Builder file access to your app’s UIApplication object. It just means that the UIViewController object (the file’s realowner) will be treated as if it were a UIApplication object, probably with unpleasant
consequences.
The principle use of the file’s owner is to gain access to the objects created in the Interface Builder file. To access any of those objects, you must obtain a reference to them. While it’s possible to obtain references to the top-level objects, all other objects must be accessed indirectly, either
via properties in the top-level objects or through connections set in the file’s owner object. In the example shown in Figure 15-4, the STGameView object becomes accessible through the owner object’s gameView property.Without a placeholder object, it would be awkward (sometimes impossible) to access the objects you just created.
When an Interface Builder file loads, only those outlets in the placeholder objects that are connected in the file are set. All other properties and outlets remain the same.
Objects within the Interface Builder file can only establish connections to other objects in the graph or to the placeholder objects. For example, an object being loaded by a view controller cannot be directly connected to theapplication delegate object. That object isn’t in the graph. The exception is the first responder. The first responder is an implied object that could be any object in the responder chain. As you learned in Chapter 4, the responderchain goes all the way to the UIApplication object.
Now that you have a feel for how objects in an Interface Builder file get created, it’s time to dig into the details of how objects are defined and connected to one another, and what that means to your app.
Creating Objects
Adding an object to an Interface Builder file is equivalent to creating that object programmatically. This is a really important concept to grasp. There is nothing “special” about objects created from Interface Builder files. You canalways write code that accomplishes the exact same results; it’s just excruciatingly tedious, which is why Interface Builder was invented in the first place.
In Figure 15-5, an object is being added to an Interface Builder file. This is borrowed from the
ColorModel project in Chapter 8.
Figure 15-5. Adding an object to an Interface Builder file
The object being added is a UISlider object. It’s being created with a frame of ((39,137),(118,34))
and it’s a subview of the root UIView. The equivalent code (in the view controller) would be:
UISlider *newSlider = [[UISlider alloc] initWithFrame:CGRectMake(39,137,118,34)]; [self.view addSubview:newSlider];
This code creates a new UISlider object with the desired dimensions and adds it to the
view controller’s root view object. In both methods (Interface Builder and programmatically) the end result is the same.
There’s only one, technical, difference between how the UISlider object gets created in the Interface Builder file and how you create one programmatically. When you write code to create a view object, you use
the -initWithFrame:initializer message. When an object is un-archived—which is how objects in an Interface Builder file get created—the object is created with an -initWithCoder: message. The coder parameter contains an object that has all of theproperties the new object needs, including its frame. You’ll learn all about -initwithCoder: in Chapter 19.
So far, you've only used Interface Builder to add objects from the object library, or custom subclasses of those library objects (most often, UIView). Using the identity inspector, you can edit the class of an object, turning itinto any custom subclass that you’ve created. But you can’t change the object’s class to just any class. Or can you?
If you poke around the object library, you’ll find a curious object: Object (see figure). It’s an NSObject object. Byitself, it’s nearly useless. But since every object you’ll ever create is a subclass of NSObject, you can use the
identityinspector to change the class of that object to anything you want.
In addition, the identity inspector has a limited ability to set the properties of your custom object. In ColorModel app, you programmatically created the CMColor object that was the app’s data model object and then set its initial property values. You could have created that object in Interface Builder. In your CMViewController.h interface,change the colorModel property so it’s an Interface Builder outlet, like this (new code in bold):
@property (strong,nonatomic) IBOutlet CMColor *colorModel;
To create the actual CMColor object, drag an Object object into the top level of the object outline
(as shown above).
Use the identity inspector to change its class to CMColor, and then connect the colorModel outlet to the newobject, as shown in the next figure. (Make the colorModel property of the CMColorView class an outlet and connect it too.)
To edit the properties of your custom object, add them to the User Defined Runtime Attributes section, as shown in the next figure. You can set any object property that’s one of these types: BOOL, any kind of number (integer or floating point), NSString, CGPoint, CGSize, CGRect, NSRange, or UIColor. Just click the + button and describe the name of the property, its type, and the value you want it set to (see next figure).
The project in the Learn iOS Development Projects ➤ Ch 15 ➤ ColorModel folder has been modified to
create, configure, and connect the CMColor object entirely in Interface Builder, as described here. Take a look at the code this displayed in -viewDidLoad:.
You can combine this technique with custom subclasses of other standard library objects. If you create a subclass UIView, you can set all of the standard UIView properties in the attributes inspector, and then use theidentity inspector to set any additional properties that your class defines.
Editing Attributes
But the frame isn’t the only property of the UISlider object. When you created your slider object in ColorModel, you used the attributes inspector to change several of its properties. You changed its maximum range to 360 andchecked the Update Events: Continuous option. That was equivalent to writing this code:
newSlider.maximumValue = 360; newSlider.continuous = YES;
Again, the resulting object is indistinguishable from the object created by the Interface Builder file, despite subtle differences in how those property values are set.
Connections
You’ve seen how objects, and their properties, in an Interface Builder file get created, but what about
connections? Figure 15-6 shows the hueSlider outlet being connected to a slider object.
Figure 15-6. Connecting an outlet in Interface Builder
Here’s the equivalent code:
self.hueSlider = newSlider;
And this time, when I say “equivalent” I mean “identical.” Objects in an Interface Builder file are created in stages. During the first stage, all of the objects are created and have their attributes set. In the next stage, all of theconnections are made. Those connections are made using the same methods you’d use to set
an outlet property programmatically.
Action connections are a little more complicated. An action connection consists of two, and possibly three,
pieces of information.
Objects that send a single action (UIGestureRecognizer, UIBarButtonItem, and so on) are connected by
setting two properties: the target and the action. The target property is the object (usually a controller) that will receive themessage. The action is the selector (-play:, -pause:,
-someoneMashedAButton:) that determines which message the target receives.
Some objects (such as UIGestureRecognizer) can be configured to send messages to multiple targets. You’d
Some objects (such as UIGestureRecognizer) can be configured to send messages to multiple targets. You’d
connect those objects,programmatically, like this:
[gestureRecognizer addTarget:viewController action:@selector(changeColor:)];
To connect additional actions, send more -addTarget:action: messages with those additional actions.
Disconnect actions using -removeTarget:action:
Other single-event objects (such as UIBarButtonItem) have only a single target property. These objects can only send a single message to a single target. You can programmatically make an action connection by setting thetarget and action properties individually, like this:
barButtonItem.target = viewController; barButtonItem.action = @selector(refresh:);
More complex control objects have a multitude of events, any of which can be configured to send action messages when they occur. A UISlider object can send action messages when: the user touches the control(UIControlEventTouchDown), they drag outside
its frame (UIControlEventTouchDragOutside), release their finger outside its frame
(UIControlEventTouchUpOutside), release their finger inside its frame (UIControlEventTouchUpInside), or the value of the slider changes (UIControlEventValueChanged). Each of these is identified by an event constant (seeUIControlEvents). Any event can be configured to send action messages to multiple targets. In Figure 15-7, the Value Changed event is being configured to send a -changeHue: message to the view controller.
Figure 15-7. Creating an action connection for the Value Changed event
The code to create that same connection looks like this:
[newSlider addTarget:viewController action:@selector
(chageHue:)
forControlEvents:UIControlEventValueChanged];
Sending Action Messages
At this point, you shouldn’t be surprised to learn that action messages can also be sent programmatically. If you want to send an action message, all you have to do is send a -sendAction:to:from:forEvent:
message to your application object ([UIApplication sharedApplication]).
Subclasses of UIControl send events by sending themselves a -sendAction:to:forEvent: message. This, incidentally, just turns around and sends -sendAction:to:from:forEvent:
to your application object, passing itself in the from: parameter.
You can programmatically cause any UIControl object to send the actions associated with one or more of its events by sending it a -sendActionsForControlEvents: message.
In all cases—both when sending action messages programmatically and when configuring control objects—the target object can be nil. When it is, the action message will be sent to the responder chain, starting with the firstresponder, instead of any specific object (see Chapter 4). To send an arbitrary message up the responder chain, use code that looks like this:
[[UIApplication sharedApplication] sendAction:@selector(orderIceCream:) to:nil
/* responder chain */ from:self forEvent:nil];
You now have a good grasp of how Interface Builder works and how objects get created, configured, and
connected. You’ve also learned most of the equivalent code for what Interface Builder does, so you could
programmaticallycreate, configure, and connect objects, as you did in the Shapely app.
Forget all of that. Well, don’t forget it-you might need it someday but set it aside for the moment.
It’s great to know how Interface Builder files work, and the code you would write to do that same work. But the point ofhaving Interface Builder is so you don’t have to do that work! Instead of writing code to replace Interface Builder, it’s time to put Interface Builder to work for you.
Taking Control of Interface Builder Files
Now that you understand what Interface Builder files are and how they work, you can easily add new ones to your app and load them when you want. This is the middle ground between the completely automatic use of Interface Builder files by view controllers and creating your view objects entirely
with code. In this section you’re going to learn to:
n Add an independent Interface Builder file to your project
n Programmatically load an Interface Builder file
n Designate multiple placeholder objects that Interface Builder objects can connect to
Back in Chapter 11 you wrote the Shapely app. Every time a button was tapped you created a new shape (SYShapeView) object, configured it, and attached a slew of gesture recognizers, using nothing but Objective-C.
Howmuch of that code could you accomplish using Interface Builder? Let’s find out.










No comments:
Post a Comment