Adding Outlets and Actions to Your RoadTrip Code - Getting Your Feet Wet: Basic Functionality - iOS App Development For Dummies (2014)

iOS App Development For Dummies (2014)

Part III. Getting Your Feet Wet: Basic Functionality

Chapter 9. Adding Outlets and Actions to Your RoadTrip Code

In This Chapter

arrow Connecting your user interface to your code

arrow Using the Assistant

arrow Taking advantage of the Connections inspector

arrow How connections are made at runtime

One of the things that the RoadTrip app will be able to do is send the image of a car to the top of the screen, have it move back down the screen, and then have it turn around so it's back where it started, all from a simple tap of the Test Drive button. This isn’t an essential for an app like RoadTrip, but it’s an interesting add-on that lets me show you how to add animation and sound to an app in the next chapter. So if you’re more interested in developing games than a data-based travel guide, you’ll be on your way.

To get ready to add sound and animation to your app, you’re going to need to add some logic to your code. You do that in a custom view controller, which gets detailed coverage in this very chapter — especially the bits about adding custom view controllers and connecting them to the view controllers you create in your storyboard. But that’s only one part of the story. For all the pieces to fit, you’ll need to be able to access the elements stipulated in your storyboard — elements like the car image — and then connect those elements to the code in your custom view controller.

image The way you add the logic to connect the view to the model via the view controller in this chapter is going to be the model for how you deal with the rest of the view controllers in the storyboard. Keep in mind that, although you can add all the view controllers you’ll ever need to the storyboard graphically, you still need to add some code on your own if you ever want the controller to actually do anything, such as get data from the model and send it to the view.

Using Custom View Controllers

The view controller provided by the storyboard is a UIViewController (or UITableViewController, which is a type of view controller) and is blissfully unaware of what you want to display in a view, or how to respond to view actions (such as the user tapping the Test Drive button). In this section, you create a custom controller that does know about its view. (In Chapter 10, you get to add the logic you need to the custom view controller to make the car move and make noise.)

Adding the custom view controller

You start the process of adding a custom view controller to your project by adding the custom view controller class, as follows:

1. To create a new group to keep your view controller classes in, select the RoadTrip group in the Project navigator and either right-click and choose New Group from the menu that appears or choose File⇒ New⇒New Group from the main menu.

Note that you need to select the RoadTrip group, right there under RoadTrip Resources, and not the RoadTrip project, which is at the top of the Project navigator.

image To change a file’s group, select the file and drag it to the group you want it to occupy. The same goes for groups as well (after all, they can go into other groups).

2. The New Group should be selected so you can name your new group View Controller Classes by typing it.

If it is not already selected, or you want to change the name, select the name and name it (this is the same way you would name a folder on the Mac).

3. In the Project navigator, select the (newly created) View Controller Classes group and either right-click and then choose New File from the menu that appears or choose File⇒New⇒New File from the main menu (or press image+N) to bring up the New File dialog.

4. In the left column of the dialog, select Cocoa Touch under the iOS heading, select Objective-C class template in the top-right pane, and then click Next.

5. In the Class field, enter TestDriveController, choose UIViewController from the Subclass Of drop-down menu, make sure that the Target for iPad option is selected and that the With XIB for User Interface option is deselected, and then click Next.

You could also name this TestDriveViewController. It is a view controller but it generally controls the test drive feature. It’s up to you. Apple engineers have gradually been moving to longer and more descriptive names for things. Even the most typing-challenged engineers can live with longer names thanks to Xcode’s Fix-it and code completion logic. I’m opting for the shortest name in part because it makes the code listings in this book a little easier for the layout (and reading!) process.

6. In the Save sheet that appears, select a location, and then click Create.

If you examine the (generated) code added in TestDriveController, you’ll notice two methods.

The first one I’ll draw your attention to is viewDidLoad — you’ll be adding code to it to do any view controller or view initialization after the view controller and its view have been loaded from the storyboard:

- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

Setting up the TestDriveController in the MainStoryboard for iPad

Now that you have a custom view controller (it doesn’t do anything yet, but it will), you need to let the storyboard know that you want to load your custom view controller rather than a UIViewController.

In the Project navigator, select the Main_iPad.storyboard file, and in the Document Outline, select View Controller – TestDrive in the View Controller – TestDrive Scene. (I had you enter TestDrive in the Title field way back when, and now you’re actually using it.)

Using the Inspector selector bar, open the Identity inspector in the Utility area and then choose TestDriveController (replacing UIViewController) from the Class drop-down menu in the Custom Class section, as I have in Figure 9-1). This means that when the Detail cell in the Master Controller is tapped, your custom controller — the TestDriveController — will be instantiated and initialized, meaning it will now receive events from the user and connect the view to the Trip model you’ll create in Chapter 11.

image

Figure 9-1: Connecting the view controller object in the iPad storyboard to the Test
Drive
Controller class.

When you write your own code, it’s pretty obvious how an app works: You create an object, initialize it, and then send it messages. When you work with storyboards, however, how your app works may not be so obvious. How do you go from the objects you added to your user interface in Interface Builder to code that enables you to access these objects? How do you get an Image view to change its image or receive a message that the user has tapped a button, for example?

The objects in your user interface must communicate with each other and with your source code if your app is to allow the user to interact with it. In order to access a user interface object and specify which messages are sent and received, you use Interface Builder to create connections. There are two basic types of connections you can create:

· Outlet connections, which connect your code to Interface Builder objects that enable you to get and set properties (change the image in an Image view, for example)

· Action connections, which specify the message to be sent to your code when the control is interacted with (the user taps a button, for example)

In this chapter, I explain how to create both outlets and actions in your code.

Understanding Outlets

An outlet is a way to connect your code to an object you added to a view in your storyboard that you’ve decided will become part of your user interface at runtime. The connections between your code and its outlets are automatically reestablished every time the object referenced by the outlet is loaded.

The object containing an outlet is often a custom controller object such as the view controller generated for you by the template. In the class declaration of that custom controller, you declare an outlet as a property with the type qualifier of IBOutlet.

Listing 9-1 shows you what the TestDriveController class extension (in the .m file) will look like after you’re done mucking about with it at the end of this chapter.

Listing 9-1: Outlets (and Actions) Declared

@interface TestDriveController () {
AVAudioPlayer *backgroundAudioPlayer;
SystemSoundID burnRubberSoundID;
BOOL touchInCar;

}
@property (weak, nonatomic) IBOutlet UIImageView *car;
@property (weak, nonatomic)
IBOutlet UIButton *testDriveButton;
- (IBAction)testDrive:(id)sender;
@end

An outlet is a property that points to another object, enabling an object in an app to communicate with another object at runtime. Using an outlet, you have a reference to an object defined in the storyboard (or a nib file) that is then loaded from that storyboard file at runtime. You can make outlet connections between any objects that you add to your storyboard in Interface Builder.

For example, in Figure 9-2, you use an outlet to get the text the user typed in the Find text field.

But before you go setting up outlets for anything and everything, make sure that you really need one. You don’t have to use outlets to be able to access all Interface Builder objects.

image

Figure 9-2: Using an outlet to get the text entered by the user.

Adding Outlets

To recap from the previous section, outlets are the way your code can access — either by sending messages or setting properties — the Interface Builder objects in your storyboard. You can do all this graphically in Interface Builder, and the required code is generated for you. Read on to find out more about how this works.

Opening the Assistant editor

To create an outlet, you need to connect the interface object in Interface Builder with your code. Although you have a couple of ways to make this connection, the easiest and most clear-cut way is to use the Assistant editor to automatically display the code file that’s most relevant to the interface element you’re working with. To make the Assistant editor automatically display a likely code file, follow these steps:

1. Select the Main_iPad.storyboard file in the Project navigator.

2. Close the Utility area if it’s open (and you need the space) by deselecting it in the View selector in the Xcode toolbar.

3. In Interface Builder, select the View Controller – TestDrive in the View Controller – TestDrive Scene (see, it is handy to actually label things) in the Document Outline; then click the Assistant Editor button in the Editor selector on the toolbar (see Figure 9-3).

The Assistant editor pane opens. If Automatic is chosen in the toolbar, you will have a choice of views in TestDriveController.h and Test DriveController.m. That’s because both files are relevant to the selected view in the Document Outline. You want to work withTestDriveController.m so, if necessary, choose it as shown in Figure 9-4.

image

Figure 9-3: Select the Assistant editor.

image

Figure 9-4: The view controller in the storyboard and the Test
Drive
Controller.m file in the Assistant editor.

image A Jump bar (a handy thing to know about) appears at the top of each Editor area pane and gives you a way to navigate through the files and symbols in your project. The configuration and behavior of each Jump bar is customized for the context in which it appears. In addition to a hierarchical path that enables you to navigate to a file or symbol in your project, the basic configuration of a Jump bar includes the following:

· image The Related Items menu (accessed by clicking the icon shown in the left margin) grants you access to additional selections relevant to the current context, such as recently opened files or the interface (.h) header file for an implementation (.m) code file you're editing.

· image Previous and Next buttons enable you to step back and forth through your navigation history.

I explain the Jump bar in detail when discussing the Standard editor in Chapter 7.

Creating the outlet

After you have the TestDriveController implementation displayed (as I do in Figure 9-4), either by having the Assistant editor display it automatically or by navigating to it using the Jump bar, the actual creating-an-outlet business using the Interface Builder editor is very straightforward and pretty easy. You do it by Control-dragging from the element you’re interested in to the TestDriveController interface, as detailed in the following steps:

1. Control-click and drag from the element in the view (the car image, in this example) to the TestDriveController.h file between the @interface and @end statements, as shown in Figure 9-5.

2. In the dialog that appears, name this outlet car (see Figure 9-6) and then click the Connect button.

The outlet is added as a property. (I explain properties in Chapter 6.)

Figure 9-7 shows your new outlet in all its glory.

image A connection between an object and its outlets is actually stored in a nib file. (Come to think of it, a storyboard is really just a series of nib files.) When the nib file is loaded, each connection is reconstituted and reestablished, thus enabling you to send messages to the object.IBOutlet is the keyword that tags an instance-variable declaration so that the Interface Builder app knows that a particular instance variable is an outlet — and can then enable the connection to it.

image

Figure 9-5: Control-drag from an object to create an outlet.

image

Figure 9-6: Name the outlet car.

image

Figure 9-7: The outlet you just created.

3. Go back to the Interface file and create an outlet for the Test Drive button (name it testDriveButton).

Control-drag from the button to the TestDriveController interface in the same way as you do the car image (between the @interface and @end statements).


image Freedom of outlet reference choice

You actually have a choice — you can add the outlet reference to the public class interface declared in the .h file, or you can add the outlet reference to the private class interface declared in the .m file. An outlet declared in the .h file will be a public property that can be used by methods in other classes in your app. An outlet declared in your.m file can only be used by methods defined in that .m file. A general programming guideline is to keep things private unless they need to be public, so you might keep your outlet references in the .m file.


image The only reason you need to create an outlet for a button (normally you’ll attach an action to the button, which you’ll do in the later section “Working with the Target-Action Design Pattern”) is to change a Button property. You’ll be using this outlet in Chapter 10 to make the button blink on and off.

The Connections inspector

While clicking and dragging is the easy way to go, you should know that you can make the same outlet connections using the Connections inspector in the Utility area, with a bit more work. But the real value of the Connections inspector is that it shows you what the outlets and received actions — covered next — and the segues actually are. To use the Connections inspector, follow these steps:

1. image Select Standard editor on the View selector in the toolbar.

The Assistant editor closes.

2. Show the Utility area by selecting it in the View selector.

3. image Select the Connections inspector by clicking its icon in the Inspector selector bar. (The icon is shown here in the left margin.)

4. In the Document Outline, select the View Controller in the Test Drive Controller – Test Drive Scene.

image

Figure 9-8: The Connections inspector and window.

In Figure 9-8, you can see that in the Outlets section of the Connections inspector, the view controller contains both car and testDriveButton outlets (as should yours, if you followed along and created them). You’ll also see in the Referencing Storyboard Segues section a Push from Table View Cell – Cell. The view outlet has always been there — it’s part of the template.

You can also Control-click the view controller in the Document Outline to get a similar picture in the Connections window.

Besides showing the outlets, if you need to change what an existing outlet points to, the Connections inspector or window is the way to go. Just drag from the circle at the end of the connection in the Outlets section in the Connections inspector or Connections window to the Interface Builder object you want that outlet to point to.

If you wanted to create an outlet without all this dragging, you would simply enter the code that Interface Builder created for you in the view controller’s @Interface (the property — don’t forget the IBOutlet):

@property (weak, nonatomic) IBOutlet UIImageView *car;

The new outlet will show up in the Connections inspector and window, and then all you need to do is drag from the circle at the end of the connection in the Outlets section to the Interface Builder object you want that outlet to point to.

image When you add outlets, you have some memory management considerations, not to mention the entire subject of properties, both of which I explain in Chapter 6. But in this chapter, I’m keeping the focus on what you need to know about interacting with Interface Builder objects in a storyboard. That interaction also includes working with a design pattern called Target-Action, which I explain next.

Working with the Target-Action Design Pattern

The other requirement of a user interface that I mention at the beginning of this chapter is being able to deal with situations where you want to connect a button to your code so that when a user taps the button, something happens. This requirement involves using the Target-Action pattern,which is one of the key design patterns in iOS programming.

You use the Target-Action pattern to let your app know that it should do something when prompted. A user might tap a button or enter some text, for example, and the app must respond in some way. The control — a button, say — sends a message (the Action message) that you specify to the target (the receiving object, which is usually a view controller object) that you have selected to handle that particular action.

Using the Target-Action pattern: It’s about controls

When a user acts on the control by, say, tapping a button, the iPhone or iPad generates an event. The event triggering a particular action message can be anything, just as the object sending the message can be any object. For example, a gesture-recognizer object might send an action message to another object when it recognizes its gesture. However, the Target-Action pattern is usually found with controls such as buttons and sliders.

The event as such probably doesn’t tell you much, but Target-Action provides a way to send an app-specific instruction to the appropriate object.

image

Figure 9-9: The Target-Action mechanism.

If you wanted to develop an app that could start a car from an iOS device (not a bad idea for those who live in a place like Hibbing, Minnesota in winter), you could display two buttons, Start and Heater. You could use Interface Builder to specify that when the user taps Start, the target is the CarController object and the method to invoke is ignition. Figure 9-9 shows the Target-Action mechanism in action.

image The Target-Action mechanism enables you to create a control object and tell it not only which object you want handling the event but also the message to send. For example, if the user taps a Ring Bell button onscreen, you want to send a Ring Bell message to the view controller. But if the Wave Flag button on the same screen is touched, you want to be able to send the Wave Flag message to the same view controller. If you couldn’t specify the message, all buttons would have to send the same message. It would then make the coding more difficult and more complex because you would have to identify which button sent the message and what to do in response. It would also make changing the user interface more work and more prone to errors.

You set a control’s action and target using Interface Builder. You get to specify what method — in which object — should respond to a control without having to write any code.

Action methods have a certain signature (format, in other words):

- (IBAction)testDrive:(id)sender;

The type qualifier IBAction, which is used in place of the void return type, flags the declared method as an action so that Interface Builder is aware of it. (This is similar to the IBOutlet tag, used in the “Creating the outlet” section, earlier in this chapter.) And just as with outlets, you can actually make the connections in the Interface Builder editor, and Xcode will generate the necessary code for you.

The sender parameter is the control object sending the action message. When responding to an action message, you may query the sender to get more information about the context of the event triggering the action message.

image You can set the action and target of a control object programmatically or in Interface Builder. Setting these properties effectively connects the control and its target via the action. If you connect a control and its target in Interface Builder, the connection is archived in a nib file. When an app later loads the nib file, the connection is restored.

image IBAction is like IBOutlet — it does nothing in the code but rather is a tag used by Interface Builder.

Adding an action

After you have the TestDriveController interface displayed, either by having the Assistant editor display it automatically or by navigating to it using the Jump bar, the actual business of creating an action is pretty straightforward. From within the Interface Builder editor, just Control-drag from the element you’re interested in (the Test Drive button, in this case) to the TestDriveController interface, as detailed in the following steps:

1. In the Project navigator, select the Main_iPad.storyboard file.

2. Close the Utility area by deselecting it in the View selector.

You don’t need it to create the action.

3. Open the Assistant editor by clicking its button in the Editor selector in the toolbar.

You should see the TestDriveController.m interface file displayed in the Assistant editor. If it doesn’t appear, navigate to it using the steps in the earlier section “Creating the outlet.”

4. Control-click and drag from the Test Drive button in the view (the car image, in this example) to the TestDriveController.m file, right between the @interface and @end statements.

5. In the dialog that appears, choose Action from the Connection drop-down menu, as shown in Figure 9-10.

6. In the same dialog, leave Touch Up Inside as the selection in the Event drop-down menu.

To create an Action for a control, you need to specify what event triggers the action. In the case of a button, a Touch Up Inside event is the usual choice because Touch Up Inside is the event that's generated when the very last place the user touched before lifting her finger is still inside the button. (This allows a user to change her mind about touching the button by moving her finger off the button before lifting it.)

7. Still in the same dialog, name this action testDrive by entering testDrive in the Name field; then click Connect.

As shown in Figure 9-11, a new action method

- (IBAction)testDrive:(id)sender;

gets added to your code.

image

Figure 9-10: Select Action as the connection type.

image

Figure 9-11: A new action message.

Xcode also adds code for you in the .m file to support whatever it is that you’re doing. (In this case, the added code supports the action.)

image You can always double-check the status of your Target-Action connections with the help of the Connections inspector, mentioned earlier in this chapter as part of my coverage of outlets. With the Utility area displayed, click the Connections inspector icon in the Inspector selector bar to open the Connections inspector or right-click the view controller in the Document Outline to get a similar picture in the Connections window. In the Received Actions section, you’ll see the new action. As I explain earlier, you can change the Interface Builder object you are receiving the action from by dragging from the circle to the new Interface Builder object. You can also add an action by entering the code the Interface Builder would have added for you (the method declaration and definition) in view controller’s @interface

- (IBAction)testDrive:(id)sender;

and @implementation

- (IBAction)testDrive:(id)sender {
}

and then dragging from the circle in the Received Actions section in the Connections inspector or window to the control and choose an Event type from the contextual menu that appears when you release the mouse button.

I’m sure you’ve noticed that RoadTrip has a bunch of other buttons that also need connecting. It turns out, though, that you won’t be using the Target-Action pattern to connect them. You’ll be using a storyboard feature called segues to do that for you. I explain using segues in Chapter 14.

How Outlets and Actions Work

You need to be able to connect the objects you added to your user interface in Interface Builder to code that enables you to access these objects (such as to an Image view to change its image) or receive a message that the user has tapped a button.

In the chapter, I show you how to create outlets and actions to do that, but I haven’t really explained how all that is connected at runtime.

Storyboards are a collection of (resource) nib files that you use to store the user interface of your app. A nib file is an Interface Builder document. When you use Interface Builder in Chapter 4 to create your user interface, you create an object graph that is saved (archived) as part of the resource file. When you load the file, the object graph is then used to re-create the relationships between the objects in the file, and your program objects as well.

Every storyboard file has an initial view controller. At runtime, it's loaded along with its view and all the other Interface Builder objects you added in Chapter 5 — and you get an exact replica of the objects that were in your Interface Builder document. The nib-loading code instantiates the objects, configures them, and reestablishes any inter-object connections including the outlets and actions that you created in Interface Builder. Not bad for a bunch of 0s and 1s, right?

Update the iPhone storyboard file

As in previous chapters, the code you added in this chapter works for both the iPhone and iPad. But, as in previous chapters, you now should add objects to the iPhone storyboard file to keep it on an equal footing with its iPad sibling. You don’t want the iPhone storyboard to think that you love it less than the iPad.

You created the Test Drive scene way back in Chapter 5. Now make sure that the TestDriveController in the iPhone storyboard file has the connections shown in Figure 9-12, which are very similar to the iPad storyboard connections shown earlier in Figure 9-8.

image

Figure 9-12: TestDriveController connections in the iPhone storyboard file.