Wednesday, 7 May 2014

Loading a Scene [If You Build It ...]


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 controllerview 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 files 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 files 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 Files 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 files owner.
This is typically the object loading the file; when a view controller loads its Interface Builder file, it declares itself as the files 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 files 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 files 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 files 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 files owner from UIViewController to UIApplication wont magically give your Interface Builder file access to your apps UIApplication object. It just means that the UIViewController object (the files realowner) will be treated as if it were a UIApplication  object, probably with unpleasant
consequences.

The principle use of the files 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 its 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 files owner object. In the example shown in Figure 15-4, the STGameView object becomes accessible through the owner objects 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 isnt in the graph. The exception ithe first responder. The first responder is an implied object that could bany object in the respondechain. 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, its 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; its 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. Its being created with a frame of ((39,137),(118,34))
and its 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 controllers root view object. In both methods (Interface Builder and programmatically) the end result is the same.

Theres 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 objects class to just any class. Or can you?

If you poke around the object library, you’ll find a curious object: Object (see figure). Its an NSObject object. Byitself, its 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 apps 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 its 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 thats one of these types: BOOL, any kind  of number (integer  or floating point), NSString, CGPoint, CGSize, CGRectNSRange, 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 isnt 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

Heres 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 (UIGestureRecognizerUIBarButtonItem, 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
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 shouldnt be surprised to learn that action messages can also be sent programmaticallyIf you wanto send an action message, all you have to do is send a -sendAction:to:from:forEvent:
message to your application objec([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, dont forget it-you might need it someday but set it aside for the moment.
Its 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 dont have to do that work! Instead of writing code to replace Interface Builder, its 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? Lets find out.

No comments:

Post a Comment