If You Build It… - Learn iOS 8 App Development, Second Edition (2014)

Learn iOS 8 App Development, Second Edition (2014)

Chapter 15. If You Build It…

Interface Builder is Xcode’s “secret sauce.” It makes the creation of complex interfaces effortless: drag interface elements into a canvas, connect them, click a button, and they become working objects in your app. It’s like magic.

Any sufficiently advanced technology is indistinguishable from magic.

—Arthur C. Clarke

“Magic” is often used to describe what we don’t understand. Interface Builder can sometimes meet this criteria. It works; you just don’t know how. Well, step behind the curtain and prepare to learn those secrets. In this chapter, you will learn the following:

· Learn what Interface Builder files are (exactly)

· Find out how objects in an Interface Builder file become objects in your app

· Discover programmatic equivalents to what Interface Builder does

· Understand how placeholder objects work

· Programmatically load your own Interface Builder files

· Provide your own placeholder objects

You’re going to learn what Interface Builder does in a very pragmatic way. I’m going to show you the code that accomplishes what you’ve been doing in storyboards. Conversely, you’ll then take some code you wrote earlier and translate that into an .xib (stand-alone Interface Builder) file.

How Interface Builder Files Work

An Interface Builder file contains a serialized graph of objects. In Chapter 14, you learned a little of what serialization means and some of the challenges involved. In Chapters 18 and 19, you’ll learn how to serialize objects and how to create objects that can be serialized (archived). But for now, all you need to know is that “serializing an object” means converting its properties into a transportable array of bytes and eventually reversing the process to get them back again.

Note I’m still using the term serialize in the generic, computer engineering sense. In the language of Cocoa Touch, Interface Builder files are an archive of objects. Loading an Interface Builder file consists of unarchiving those objects.

So, what’s an object graph? An object graph is a set formed by an object, all of the objects that object refers to, all of the objects those objects refer to, and so on. The object—or small number of objects—that begins the graph is referred to as the root or top-level object(s).

Serialization starts at the root object. That object converts its properties into a serialized byte array. If any of its properties refer to other objects, those objects are asked to serialize their property values into the same byte array, and so on, until all of the objects and property values for the entire graph are converted. The finished array of bytes describe the entire set of objects, their properties, and their relationships.

Compiling Interface Builder Files

You use an Interface Builder file by creating one and adding it to your project. You then edit it and build your app.

But the .xib or .storyboard file that you edit in Xcode is not what ends up in your app’s bundle. Like your source (.swift) files, your Interface Builder files are compiled. The nib compiler converts the design in your .storyboard or .xib file into serialized data that, when unarchived, will create the objects with the properties and connections you described. This compiled nib file is then added to your app’s bundle as a resource file.

Note Some interface editors take your design and turn it into source code, equivalent to what you drew, which you then compile as part of your app. These tools are called code generators. Interface Builder is not a code generator. Interface Builder is an object compiler.

Loading a Scene

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.storyboard file (from MyStuff in Chapter 7). Figure 15-2 shows the (simplified) graph of objects contained in that scene.

image

Figure 15-1. Detail View scene in Interface Builder

image

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 an array). Some of those view objects refer to additional objects, such as NSString, UIImage, and UIGestureRecognizer objects.

The instantiateViewControllerWithIdentifer(_:) function instigates the re-creation (unarchiving) of the view controller and all of its related objects stored in the storyboard scene. This method is invoked automatically when triggered by a segue; or, as you did in the Wonderland app (Chapter 12), you can call it programmatically to create the view controller for a scene when it pleases you.

During deserialization (unarchiving), the property values and connections in the serialized data are used to instantiate new objects, set their properties, and connect them.

Loading an .xib File

Storyboards are the modern method of designing iOS apps. But iOS will also let you design an interface in a separate .xib file. A view controller will load an .xib file automatically, or you can load the objects in an .xib file programmatically. When using an .xib file, 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 unarchiving that single object. If this interface were stored in an .xib file, its object graph would be a little different, as shown inFigure 15-3.

image

Figure 15-3. Graph of objects in an .xib file

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 unarchiving process, your existing objects are substituted for the placeholders. The existing objects become part of the object graph but are not created by the .xib file. Outlets in a placeholder can be set to objects created in the file, and objects in the file can be connected to a placeholder. In Figure 15-3, the view and nameField properties in the file’s owner (the view controller) are still set to the UIView and UITextField objects.

The important difference to grasp is this: when a view controller is loaded from a storyboard scene, the view controller and all of its connected objects are created for you, en masse. When using an .xib file, it’s a two-stage process. The owner object—typically the view controller, but it doesn’t have to be—is created first, often programmatically. Then the .xib file is loaded. The .xib file creates all the remaining objects and connects those objects to the existing owner object.

Placeholder Objects and the File’s Owner

When an Interface Builder file is loaded, the caller 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 want. (Later in this chapter you’ll load an Interface Builder file with multiple placeholders.) Think of the 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 with the class of the owner object when the file is loaded. You set the class of the file’s owner using the identity inspector in 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 real owner) will be treated as if it were aUIApplication object, probably with unpleasant consequences.

Caution When changing the class of any placeholder object in Interface Builder, ensure that you set it to the class, or a superclass, of the actual object that will be supplied when the file is loaded.

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-3, the UIView object becomes accessible through the owner object’s view property. Without a placeholder object outlet, 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 the application 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 responder chain 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 can always write code that accomplishes the same results; it’s just excruciatingly tedious, which is why Interface Builder was invented in the first place.

In Figure 15-4, an UISlider object is being added to an Interface Builder file. This is borrowed from the ColorModel project in Chapter 8.

image

Figure 15-4. Adding an object to an Interface Builder file

The slider will be created with a frame of ((55,116),(506,147)), and it’s a subview of the root UIView. The equivalent code (in a view controller) would be as follows:

let newSlider = UISlider(frame: CGRect(x: 55, y: 116, width: 451, height: 31))
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.

Note Interface Builder understands a few special object relationships and creates those relationships for you. For example, when you add a view object as a subview, it’s equivalent to calling addSubview(_:) on the superview. If you add Bar Button Items to a toolbar, the equivalent function would be setItems(_:,animated:). Dropping a new gesture recognizer into a view is the same as calling addGestureRecognizer(_:). Adding constraints is equivalent to calling addConstraint(_:) or addConstraints(_:), and so on.

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 its init(frame:) initializer function. When an object is unarchived—which is how objects in an Interface Builder file get created—the object is created using its init(coder:) initializer function. The coder parameter contains an object that has all of the properties the new object needs, including its frame. You’ll learn all about init(coder:) in Chapter 19.

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 and checked the Update Events: Continuous option. That was equivalent to writing the following code:

newSlider.maximumValue = 360.0
newSlider.continuous = true

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.

Creating Custom Objects

You can also ask Interface Builder to create custom objects that you’ve created, and you’ve done this several times already. You begin by adding the base class object, like a UIViewController object, to your interface and then use the identity inspector to change the class of the object to the one you want created. In Chapter 4, for example, you changed the root UIView in the Touchy project into a TouchyView object, with all of the methods, properties, actions, and outlets you defined.

What you might not have noticed is an obscure item in the object library named, simply, Object (see Figure 15-5). That’s right, it’s just an object. By itself, it’s nearly useless. But since every object is an object, you can drop one into your design and then use the identity inspector to change its class to just about anything you want.

image

Figure 15-5. Adding an Object object

Let’s take the ColorModel project from Chapter 8 as an example. The view controller created the data model object and set its initial property values using the following code:

var colorModel = Color()

...

colorModel.hue = 60.0
colorModel.saturation = 50.0
colorModel.brightness = 100.0

You can accomplish the same using Interface Builder. Start by adding a generic object to your design, as shown in Figure 15-5. You must drop the object into the outline because an object is not a view object and can’t be added to the visual representation of the interface.

Once you’ve added the object, select it and use the identity inspector to change its class to Color, as shown on the right in Figure 15-6. Now the storyboard will create your data model object when the view controller scene is loaded.

image

Figure 15-6. Changing the class of a generic object

But the object still isn’t connected to anything or configured. Interface Builder only makes connections to outlets and actions. Change the data model property in ViewController.swift so it’s an outlet, as follows (modified code in bold):

@IBOutlet var colorModel: Color!

Now you can connect the view controller to its data model object right in the storyboard file, as shown in Figure 15-7.

image

Figure 15-7. Connection the view controller to the data model

The only things missing are the Color object’s properties. Interface Builder has you covered here too. Open the Color.swift file and add @IBInspectable keywords to all the three properties as follows (new code in bold):

@IBInspectable dynamic var hue: Float = 0.0
@IBInspectable dynamic var saturation: Float = 0.0
@IBInspectable dynamic var brightness: Float = 0.0

The @IBInspectable keyword makes a property editable using the attributes inspector. Now these properties can be set directly in Interface Builder, as shown in Figure 15-8.

image

Figure 15-8. Editing custom attributes

Interface Builder can inspect Boolean, integer, floating point, string, point (CGPoint), size (CGSize), rectangle (CGRect), range (NSRange), and color (UIColor) properties. As of this writing, it cannot edit any other types, most notably attributed strings, nor can it edit enum types, even ones with editable raw values. Ideally, that latter restriction will change someday soon.

@IBInspectable isn’t limited to the Object object. You can designate an inspectable property in any class and it will appear in Interface Builder, alongside your actions, outlets, and any inherited attributes.

These simple techniques open up a world of possibilities. You can define all kinds of custom objects and then create, connect, and configure them right in Interface Builder.

Tip It’s also possible to design custom view objects and have them appear in Interface Builder. You do this by creating your custom view in a framework (see Chapter 21) and using the @IBDesignable keyword. Interface Builder will then load the framework and use your custom display code to draw the view—exactly as it will appear in your app—in the Interface Builder canvas. Search the Xcode documentation for sample projects that use the @IBDesignable keyword.

Connections

You’ve seen how objects and their properties in an Interface Builder file get created, but what about connections? Figure 15-9 shows the hueSlider outlet being connected to a slider object.

image

Figure 15-9. Connecting an outlet in Interface Builder

Here’s the equivalent code:

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 the connections are made. Those connections are made using the same methods you’d use to set an outlet property programmatically.

Making Action Connections

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 the call. The action is the selector (play:,pause:, someoneMashedAButton:) that determines which function gets called. Some objects (such as UIGestureRecognizer) can be configured to send actions to multiple targets. You’d connect those objects, programmatically, using code like this:

gestureRecognizer.addTarget(viewController, action:"changeColor:")

Note In Shapely, you programmatically created gesture recognizer objects, but you set the target and action when you created the object. That works too.

To connect additional actions, call the addTarget(_:,action:) function again for each. Disconnect an action using removeTarget(_:,action:).

Single-event objects (such as UIBarButtonItem) have only a single target property. These objects can only send a single action to one target. You can programmatically make an action connection by setting the target and action properties individually, like this:

barButtonItem.target = viewController
barButtonItem.action = "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 (.TouchDown), they drag outside its frame (.TouchDragOutside), they release their finger outside its frame (.TouchUpOutside), they release their finger inside its frame (.TouchUpInside), or the value of the slider changes (.ValueChanged). Each of these is identified by an event constant (see UIControlEvents). Any event can be configured to send action messages to multiple targets.

In the ColorModel project, you connected the Value Changed event of the slider to the changeHue: action of the view controller in Interface Builder. The following code creates that same connection:

newSlider.addTarget( viewController,
action: "changeHue:",
forControlEvents: .ValueChanged)

Tip UIControlEvents is a set of bits. Combine (OR) multiple constants to attach an action message to multiple events at once.

Sending Action Messages

At this point, you shouldn’t be surprised to learn that actions can also be sent programmatically. If you want to send an action, all you have to do is call the sendAction(_:,to:,from:,forEvent:) function of your application object (UIApplication.sharedApplication()).

Subclasses of UIControl send actions by calling their sendAction(_:,to:,forEvent:) function. This, incidentally, just turns around and calls sendAction(_:.to:,from:,forEvent:) on the application object, passing itself in the from: parameter. If you’re creating a custom UIControl object and it’s time to send your control’s action, that’s the function to call.

Tip If you’re sending an action in response to an iOS event (Chapter 4), it’s polite to include the UIEvent object in the forEvent: parameter. Otherwise, pass nil.

You can programmatically cause any UIControl object to send the actions associated with one or more of its events by calling sendActionsForControlEvents(_:).

In all cases—both when sending action programmatically and when configuring control objects—the target object can be nil. When it is, the action will be sent to the responder chain, starting with the first responder, instead of any specific object (see Chapter 4). To send an arbitrary message up the responder chain, use code that looks like the following:

UIApplication.sharedApplication().sendAction( "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 programmatically create, 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 of having Interface Builder is so you don’t have to do that work! Instead of writing code to replace Interface Builder, it’s time to use Interface Builder instead of writing code.

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 the following:

· Add an independent Interface Builder file to your project

· Programmatically load an Interface Builder file

· Designate multiple placeholder objects that Interface Builder objects can connect to

In Chapter 11 you wrote the Shapely app. Every time a button was tapped, you created a new shape (ShapeView) object, configured it, and attached a slew of gesture recognizers, using nothing but Swift. How much of that code could you accomplish using Interface Builder? Let’s find out.

Declaring Placeholders

Starting with the finished Shapely project from Chapter 11, add an Interface Builder file to your project by dragging a View template from the template library into your navigator, as shown in Figure 15-10. Alternatively, you can select the New File command and choose the View template from the template picker. You’ll find it under the iOS image User Interface group. Name the file SquareShape. You now have a stand-alone Interface Builder (SquareShape.xib) file that creates a single UIView object.

image

Figure 15-10. Adding a new Interface Builder file

Your .xib file will need an owner object. Instead of using an existing object, let’s create one just for this purpose. Add a new Swift class file (from the template library) and name it ShapeFactory. Edit the file to create a stub for the class using the following code:

import UIKit

class ShapeFactory: NSObject {
@IBOutlet var view: ShapeView! = nil
@IBOutlet var dblTapGesture: UITapGestureRecognizer! = nil
@IBOutlet var trplTapGesture: UITapGestureRecognizer! = nil
}

The ShapeFactory class will be the file’s owner. This is your first placeholder object. To use the owner object, select the new SquareShape.xib file in the navigator, select the File's Owner in the placeholder group, and use the identity inspector to change its class toShapeFactory, as shown in Figure 15-11. I also edited the object’s label so I know what it is.

image

Figure 15-11. Setting the class of the File’s Owner

You also need to connect objects—specifically, some gesture recognizers—to your view controller. To accomplish that, you’ll need a second placeholder object. From the object library, locate the External Object object and drag it into the outline, as shown on the left in Figure 15-12. Anexternal object is a placeholder for an object that you’ll provide when you load the .xib file.

image

Figure 15-12. Adding an external object placeholder

Select it and change its class to ViewController in the identity inspector. With the external object still selected, use the attributes inspector to assign it an identifier of viewController. Additional placeholder objects are identified by name, so you must assign each a unique identifier string.

You now have an .xib file that creates a UIView object and expects to have access to two existing objects when it loads—a ShapeFactory object and a ViewController object.

The plan is as follows:

1. You’re going to change the class of the UIView object so it creates a ShapeView object instead.

2. You’ll configure the ShapeView object with the properties you want.

3. You’ll connect the shape view to the ShapeFactory’s view outlet so the factory object has easy access to the new view object.

4. You’re going to design the four gesture recognizer objects in the .xib file, adding them to the ShapeView object and connecting their actions to the view controller placeholder.

5. You’ll connect two of the gesture recognizers to the shape factory placeholder so the factory object can do some extra housekeeping that can’t be done in the Interface Builder file.

The finished file will look, conceptually, like the object graph in Figure 15-13.

image

Figure 15-13. SquareShape.xib object graph

Designing ShapeView

The first two steps in the plan are to turn the UIView object currently in the .xib file into a completely configured ShapeView object. Select the single view object in the SquareShape.xib file and, using the identity inspector, change its class to ShapeView. Step 1 accomplished.

Switch to the attributes inspector. Xcode doesn’t really know what you’re going to use the objects in an Interface Builder file for. By default, it assumes that a top-level view object will become the root view of an interface, so it sizes the view as if it were an iPhone or iPad screen and adds a simulated status bar. For ShapeView, that isn’t the case, so turn all of these assumptions off. Change the simulated size to Freeform and status bar to None, as shown in Figure 15-14. Now use the attribute and size inspectors to set the following properties:

· Set the background to Default (none).

· Uncheck the Opaque property.

· Make sure Clears Graphics Context is checked.

· Set its size to 100 by 100 points.

image

Figure 15-14. Designing the ShapeView object

You’ve now replicated the work of creating a new ShapeView object using an .xib file—all, that is, except for the shape property, which you’ll address in a moment.

Select the ShapeView.swift file and make the shape property a variable instead of a constant, with the following code (new code in bold):

var shape: ShapeSelector = .Square

This is done because you won’t be setting its shape when the object is created (in the .xib file), so it has to be a mutable property. While still in the ShapeView.swift file, make the following changes:

1. Discard the definitions for initialSize and alternateHeight. These were used to define the view’s initial size, which is now declared in the .xib file.

2. Delete the entire init(shape:,origin:) initializer function. The view will now be created and configured entirely in the .xib file.

3. You can also delete the init(coder:) initializer function too. Since your view class no longer has any custom initializers, you don’t need to declare this one either.

See how much code you’ve already eliminated? The entire purpose of the init(shape:,origin:) initializer function was to create and configure a new ShapeView object. Most of that work is now being done in your new Interface Builder file.

Connecting the Gesture Recognizers

Back in the SquareShape.xib file, it’s time to add the gesture recognizers. From the object library, drag out a Pan Gesture Recognizer and drop it into the Shape View object, as shown in Figure 15-15. Select the recognizer object and use the attributes inspector to set its minimum and maximum touches to 1.

image

Figure 15-15. Adding the pan gesture recognizer

Switch to the connections inspector and connect its Sent Action to the moveShape: action in the view controller placeholder, as shown in Figure 15-16.

image

Figure 15-16. Connecting the pan gesture recognizer action

You’ve now created a pan gesture recognizer that recognizes only single-finger drag gestures. It’s attached to the shape view object, and it sends a moveShape: action to the view controller, when triggered. The resulting gesture recognizer object is identical to the one you created, configured, and connected in the addShape(_:) function of ViewController.

Add the other three gesture recognizers, as follows:

1. Drop a Pinch Gesture Recognizer into the shape view.

a. Connect its sent action to the view controller’s resizeShape: action.

2. Drop a Tap Gesture Recognizer into the shape view.

a. Set its Taps to 2.

b. Set its Touches to 1.

c. Connect its sent action to the changeColor: action.

3. Drop a Tap Gesture Recognizer into the shape view.

a. Set its Taps to 3.

b. Set its Touches to 1.

c. Connect its sent action to the sendShapeToBack: action.

Much of the code you wrote in the addShape(_:) function has now been replicated using Interface Builder. There are two steps that can’t be accomplished in Interface Builder; you’ll address those in code shortly.

Building Your Shape Factory

Your shape factory object defines outlets that need to be connected to the shape view and selected gesture recognizers. Select the SquareShape.xib file, select the File’s Owner (or Shape Factory, if you renamed it), and use the connections inspector to connect the shapeView,dblTapGesture, and trplTapGesture outlets to their respective objects, as shown in Figure 15-17. Save the file. (Seriously, save the file by choosing File image Save; it’s important.)

image

Figure 15-17. Connecting the factory outlets

Tip Make sure you connect the right gesture recognizer outlet to the correct object because both objects appear as Tap Gesture Recognizer in the outline. If you have Interface Builder objects that might be easily confused, use the identity inspector to change the object’s label to something more descriptive. In Figure 15-17, I changed their labels to Double Tap… and Triple Tap… so I can tell which one is which. Object labels are cosmetic and don’t alter the functionality of your Interface Builder design in any way.

The one aspect—sorry for the bad pun—that has not been addressed is the difference between the square, rectangle, circle, and oval shapes. If you remember, init(shape:,origin:) would produce a 100-by-50-point view for rectangle and oval shapes and a 100-by-100-point view for everything else. In this version, you’re going to replicate that logic using two Interface Builder files. ShapeFactory will choose which one to load.

Start by creating the second Interface Builder file. Select the SquareShape.xib file and choose the Edit image Duplicate command, as shown in Figure 15-18.

image

Figure 15-18. Creating the RectangleShape.xib file

Name the file RectangleShape. Select the new file, select the shape view object, and use the size inspector to change the height of the shape view to 50. Now you have two Interface Builder files, one that produces a 100-by-100 view and a second one that creates a 100-by-50 view.

Now switch to the ShapeFactory.swift file. Add a class method that will choose which Interface Builder file (SquareShape or RectangleShape) to load for given shape, as follows:

class func nibNameForShape(shape: ShapeSelector) -> String {
switch shape {
case .Rectangle, .Oval:
return "RectangleShape"
default:
return "SquareShape"
}
}

Loading an Interface Builder File

You’re now ready to create your shape view and gesture recognizer objects by loading an Interface Builder file. (It’s about time!) Add the -loadShape:forViewController: method now.

func load(# shape: ShapeSelector, inViewController viewController: UIViewController) image
-> ShapeView {
let placeholders = [ "viewController": viewController ]
let options = [ UINibExternalObjects: placeholders ]
NSBundle.mainBundle().loadNibNamed( ShapeFactory.nibNameForShape(shape),
owner: self,
options: options)
assert(view != nil, "shape view not connected in xib file")
view.shape = shape
dblTapGesture.requireGestureRecognizerToFail(trplTapGesture)

return view!
}

The first two statements prepare the view controller to be a placeholder object when the Interface Builder file is loaded. You may pass as many placeholder objects as you like; just make sure their classes and identifiers agree with the external objects you defined in the Interface Builder file.

The third statement is where the magic happens. The loadNibNamed(_:,owner:,options:) function searches your app’s bundle for an Interface Builder file with that name. The name (SquareShape or RectangleShape) is determined by the nibNameForShape(_:)function you added earlier. The owner parameter becomes the file’s owner placeholder object. The options parameter is a dictionary of special options. In this case, the only special option is additional placeholder objects (UINibExternalObjects).

When loadNibNamed(_:,owner:,options:) is called, the owner and any additional placeholder objects take the place of the File's Owner and the corresponding external objects defined in the file. The objects in the file are created, the properties of the objects are set according to the attributes you edited, and finally all of the outlet and action connections are established.

Tip If you have code that needs to execute when your objects are created by an Interface Builder file, override your object’s awakeFromNib() function. When an Interface Builder file or scene is loaded, every object it creates receives an awakeFromNib() call. This occurs after all properties and connections have been set.

The function returns an array containing all of the top-level objects created in the file. You can access the objects created by the file either through this array or via outlets that you connected to the placeholders. In this app, you’ve used the latter technique.

Note The main reason you created the ShapeFactory class was to provide an owner object with outlets that conveniently provide references to the shape view and recognizer objects you’re interested in. Another solution would be to make the view controller the file’s owner and dig through the returned array of top-level objects to find the shape view and recognizer objects.

The last two statements take care of the two steps that can’t be accomplished in Interface Builder. The shape property of the view is set, and the double-tap/triple-tap dependency is established.

Replacing Code

Switch to the ViewController.swift file. Find the addShape(_:) function and replace the code that programmatically created a new ShapeView object with the following (modified code in bold):

@IBAction func addShape(sender: AnyObject!) {
if let button = sender as? UIButton {
if let shapeSelector = ShapeSelector(rawValue: button.tag) {
let shapeView = ShapeFactory().load(shape: shapeSelector,
inViewController: self)

Note ShapeFactory is an example of a helper class. A helper is an object whose sole purpose is to assist other objects in accomplishing some task. This is often logic that doesn’t fit well with the purpose or responsibilities of those other classes.

Now, for the fun part. Find the rest of the code in addShape(_:) that creates, configures, and connects the four gesture recognizers and delete all of it. You don’t need any of that now. All four of the gesture recognizers were created, configured, and connected to both the view and the view controller by the Interface Builder file.

Run the finished app and observe the results. You shouldn’t be able to tell any difference between this version of Shapely and the one from Chapter 11, which is the point. This exercise bordered on the trivial, but it demonstrated all of the key ways in which you can use Interface Builder files directly, along with their advantages and disadvantages.

· Objects are easy to create, configure, and connect in Interface Builder. This reduces the amount of code you have to write, saving time, and potentially reducing bugs. (This is an advantage).

· There are some properties and object relationships—such as the double-tap/triple-tap dependency—that cannot be set in Interface Builder and must be performed programmatically. (This is a disadvantage).

· You can easily choose between multiple Interface Builder files. Instead of writing huge if/else or switch statements, you can create a completely different set of objects simply by selecting a different Interface Builder file. (This is an advantage).

· You’re limited to the configuration and initialization methods supported by Interface Builder. In Shapely, you had to prove a settable shape property in order to “fix” the object after it was created, since you could no longer use the init(shape:,origin:)function. (This is an advantage).

· Interface Builder makes it easy to create complex sets of objects, especially ones like gesture recognizers and layout constraints. It often requires pages of dense, difficult-to-read code to reproduce the layout constraints required for many interfaces. (This is a huge advantage).

· It can sometimes take considerable effort to obtain references to the objects created in an Interface Builder file. You might have to create special placeholder objects or tediously dig through the top-level objects returned byloadNibNamed(_:,owner:,options:). In this section you created a class (ShapeFactory) for the sole purpose of providing outlets for the references to the shape view and gesture recognizers. (This is sometimes a disadvantage).

Interface Builder files aren’t the best solution for every interface; sometimes a few lines of well-written code are all you need. But in many cases, Interface Builder can save you from writing, maintaining, and debugging (literally) thousands of lines of code. It’s an amazingly flexible and efficient tool that can free you from hours of work and improve the quality of your apps. You just needed to know how it works and how to put it work for you.

Summary

Interface Builder is one of the cornerstones of Xcode, and it’s what makes iOS app development so smooth. Loading Interface Builder files directly is where the real flexibility of Interface Builder becomes evident. You now know how to define practically any interface, a fragment of an interface, or just some arbitrary objects in an Interface Builder file and load them when, and where, you want. You know how to create any kind of object you like, set its custom properties, and connect it with existing objects in your app. That’s an incredibly useful tool to have at your fingertips.