Handling Basic Interaction - Beginning iPhone Development: Exploring the iOS SDK, Seventh Edition (2014)

Beginning iPhone Development: Exploring the iOS SDK, Seventh Edition (2014)

Chapter 4. Handling Basic Interaction

Our Hello World application was a good introduction to iOS development using Cocoa Touch, but it was missing a crucial capability—the ability to interact with the user. Without that, our application is severely limited in terms of what it can accomplish.

In this chapter, we’re going to write a slightly more complex application—one that will feature two buttons as well as a label (see Figure 3-1). When the user taps either of the buttons, the label’s text will change. This may seem like a rather simplistic example, but it demonstrates the key concepts involved in creating interactive iOS apps. Just for fun, we’re also going to introduce you to the NSAttributedString class, which lets you use styled text with many Cocoa Touch GUI elements.

image

Figure 3-1. The simple two-button application we will build in this chapter

The Model-View-Controller Paradigm

Before diving in, a bit of theory is in order. The designers of Cocoa Touch were guided by a concept called Model-View-Controller (MVC), which is a very logical way of dividing the code that makes up a GUI-based application. These days, almost all object-oriented frameworks pay a certain amount of homage to MVC, but few are as true to the MVC model as Cocoa Touch.

The MVC pattern divides all functionality into three distinct categories:

· Model: The classes that hold your application’s data.

· View: Made up of the windows, controls, and other elements that the user can see and interact with.

· Controller: The code that binds together the model and view. It contains the application logic that decides how to handle the user’s inputs.

The goal in MVC is to make the objects that implement these three types of code as distinct from one another as possible. Any object you create should be readily identifiable as belonging in one of the three categories, with little or no functionality that could be classified as being either of the other two. An object that implements a button, for example, shouldn’t contain code to process data when that button is tapped, and an implementation of a bank account shouldn’t contain code to draw a table to display its transactions.

MVC helps ensure maximum reusability. A class that implements a generic button can be used in any application. A class that implements a button that does some particular calculation when it is clicked can be used only in the application for which it was originally written.

When you write Cocoa Touch applications, you will primarily create your view components using Interface Builder, although you will also modify, and sometimes even create, your user interfaces from code.

Your model will be created by writing Objective-C classes to hold your application’s data or by building a data model using something called Core Data, which you’ll learn about in Chapter 13. We won’t be creating any model objects in this chapter’s application because we do not need to store or preserve data. However, we will introduce model objects as our applications get more complex in future chapters.

Your controller component will typically be composed of classes that you create and that are specific to your application. Controllers can be completely custom classes (NSObject subclasses), but more often they will be subclasses of one of several existing generic controller classes from the UIKit framework, such as UIViewController (as you’ll see shortly). By subclassing one of these existing classes, you will get a lot of functionality for free and won’t need to spend time recoding the wheel, so to speak.

As we get deeper into Cocoa Touch, you will quickly start to see how the classes of the UIKit framework follow the principles of MVC. If you keep this concept in the back of your mind as you develop, you will end up creating cleaner, more easily maintained code.

Creating Our Project

It’s time to create our next Xcode project. We’re going to use the same template that we used in the previous chapter: Single View Application. By starting with this simple template again, it will be easier for you to see how the view and controller objects work together in an iOS application. We’ll use some of the other templates in later chapters.

Launch Xcode and select File image New image Project. . . or press imageN. Select the Single View Application template, and then click Next.

You’ll be presented with the same options sheet that you saw in the previous chapter. In the Product Name field, type the name of our new application, Button Fun. The Organization Name, Company Identifier, and Language fields should still have the values you used in the previous chapter (Apress, com.apress, and Objective-C), so you can leave those alone. In this chapter, we are going to use Auto Layout to create an application that works on all iOS devices, so in the Devices field, select Universal. Figure 3-2 shows the completed options sheet.

image

Figure 3-2. Naming your project and selecting options

Hit Next, and you’ll be prompted for a location for your project. You can leave the Create Git repository check box checked or unchecked, whichever you prefer. Press Create and save the project with the rest of your book projects.

Looking at the View Controller

A little later in this chapter, we’ll design a view (or user interface) for our application using Interface Builder, just as we did in the previous chapter. Before we do that, we’re going to look at and make some changes to the source code files that were created for us. Yes, Virginia, we’re actually going to write some code in this chapter.

Before we make any changes, let’s look at the files that were created for us. In the Project Navigator, the Button Fun group should already be expanded; but if it’s not, click the disclosure triangle next to it (see Figure 3-3).

image

Figure 3-3. The Project Navigator showing the class files that were created for us by the project template

The Button Fun folder should contain four source code files (the ones that end in .h or .m) along with a storyboard file, a launch screen file, and an asset catalog for containing any images our app needs. The four source code files implement two classes that our application needs: our application delegate and the view controller for our application’s only view.

We’ll look at the application delegate a little later in the chapter. First, we’ll work with the view controller class that was created for us.

The controller class called ViewController is responsible for managing our application’s view. The name identifies that this class is, well, a view controller. Click ViewController.h in the Project Navigator and take a look at the contents of the class’s header file:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

Not much to it, is there? ViewController is a subclass of UIViewController, which is one of those generic controller classes we mentioned earlier. It is part of the UIKit framework, and by subclassing this class, we get a bunch of functionality for free. Xcode doesn’t know what our application-specific functionality is going to be, but it does know that we’re going to have some, so it has created this class for us to write that application-specific functionality.

Understanding Outlets and Actions

In Chapter 2, you used Xcode’s Interface Builder to design a simple user interface. A moment ago, you saw the shell of a view controller class. There must be some way for the code in this view controller class to interact with the objects in the storyboard, right?

Absolutely! A controller class can refer to objects in a storyboard or nib file by using a special kind of property called an outlet. Think of an outlet as a pointer that points to an object within the user interface. For example, suppose you created a text label in Interface Builder (as we did inChapter 2) and wanted to change the label’s text from within your code. By declaring an outlet and connecting that outlet to the label object, you would then be able to use the outlet from within your code to change the text displayed by the label. You’ll see how to do just that in this chapter.

Going in the opposite direction, interface objects in our storyboard or nib file can be set up to trigger special methods in our controller class. These special methods are known as action methods (or just actions). For example, you can tell Interface Builder that when the user taps a button, a specific action method within your code should be called. You could even tell Interface Builder that when the user first touches a button, it should call one action method; and then later, when the finger is lifted off the button, it should call a different action method.

Xcode supports multiple ways of creating outlets and actions. One way is to specify them in our source code before using Interface Builder to connect them with our code. Xcode’s Assistant View gives us a much faster and more intuitive approach that lets us create and connect outlets and actions in a single step, a process we’re going to look at shortly. But before we start making connections, let’s talk about outlets and actions in a little more detail. Outlets and actions are two of the most basic building blocks you’ll use to create iOS apps, so it’s important that you understand what they are and how they work.

Outlets

Outlets are special Objective-C class properties that are declared using the keyword IBOutlet. Declaring an outlet is done either in your controller’s class header file or in a special section (called the class extension) of your controller’s implementation file. It might look something like this:

@property (weak, nonatomic) IBOutlet UIButton *myButton;

This example is an outlet called myButton, which can be set to point to any button in the user interface.

The IBOutlet keyword isn’t built into the Objective-C language. It’s a simple C preprocessor definition in a system header file, where it looks something like this:

#ifndef IBOutlet
#define IBOutlet
#endif

Confused? IBOutlet does absolutely nothing as far as the compiler is concerned. Its sole purpose is to act as a hint to tell Xcode that this is a property that we’re going to want to connect to an object in a storyboard or nib file. Any property that you create and want to connect to an object in a storyboard or nib file must be preceded by the IBOutlet keyword. Fortunately, Xcode will now create outlets for us automatically.

OUTLET CHANGES

Over time, Apple has changed the way that outlets are declared and used. Since you may run across older code at some point, let’s look at how outlets have changed.

In the first version of this book, we declared both a property and its underlying instance variable for our outlets. At that time, properties were a new construct in the Objective-C language, and they required you to declare a corresponding instance variable:

@interface MyViewController : UIViewController
{
UIButton *myButton;
}
@property (weak, nonatomic) UIButton *myButton;
@end

Back then, we placed the IBOutlet keyword before the instance variable declaration:

IBOutlet UIButton *myButton;

This was how Apple’s sample code was written at the time. It was also how the IBOutlet keyword had traditionally been used in Cocoa and NeXTSTEP.

By the time we wrote the second edition of the book, Apple had moved away from placing the IBOutlet keyword in front of the instance variable, and it became standard to place it within the property declaration:

@property (weak, nonatomic) IBOutlet UIButton *myButton;

Even though both approaches continued to work (and still do), we followed Apple’s lead and changed the book code so that the IBOutlet keyword was in the property declaration rather than in the instance variable declaration.

When Apple switched the default compiler from the GNU C Compiler (GCC) to the Low Level Virtual Machine (LLVM) recently, it stopped being necessary to declare instance variables for properties. If LLVM finds a property without a matching instance variable, it will create one automatically. As a result, in this edition of the book, we’ve stopped declaring instance variables for our outlets altogether.

All of these approaches do exactly the same thing, which is to tell Interface Builder about the existence of an outlet. Placing the IBOutlet keyword on the property declaration is Apple’s current recommendation, so that’s what we’re going to use. But we wanted to make you aware of the history in case you come across older code that has the IBOutlet keyword on the instance variable.

You can read more about Objective-C properties in the book Learn Objective-C on the Mac by Scott Knaster, Waqar Malik, and Mark Dalrymple (Apress, 2012) and in the document called Introduction to the Objective-C Programming Language, available from Apple’s Developer web site at http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC.

Actions

In a nutshell, actions are methods that are declared with a special return type, IBAction, which tells Interface Builder that this method can be triggered by a control in a storyboard or nib file. The declaration for an action method will usually look like this:

- (IBAction)doSomething:(id)sender;

It might also look like this:

- (IBAction)doSomething;

The actual name of the method can be anything you want, but it must have a return type of IBAction, which is the same as declaring a return type of void. A void return type is how you specify that a method does not return a value. Also, the method must either take no arguments or take a single argument, usually called sender. When the action method is called, sender will contain a pointer to the object that called it. For example, if this action method was triggered when the user tapped a button, sender would point to the button that was tapped. The sender argument exists so that you can respond to multiple controls using a single action method. It gives you a way to identify which control called the action method.

Tip There’s actually a third, less frequently used type of IBAction declaration that looks like this:

- (IBAction)doSomething:(id)sender
forEvent:(UIEvent *)event;

We’ll begin talking about control events in the next chapter.

It won’t hurt anything if you declare an action method with a sender argument and then ignore it. You will likely see a lot of code that does just that. Action methods in Cocoa and NeXTSTEP needed to accept sender whether they used it or not, so a lot of iOS code, especially early iOS code, was written that way.

Now that you understand what actions and outlets are, you’ll see how they work as we design our user interface. Before we start doing that, however, we have one quick piece of housekeeping to do to keep everything neat and orderly.

Cleaning Up the View Controller

Single-click ViewController.m in the Project Navigator to open the implementation file. As you can see, there’s a small amount of boilerplate code in the form of viewDidLoad and didReceiveMemoryWarning methods that were provided for us by the project template we chose. These methods are commonly used in UIViewController subclasses, so Xcode gave us stub implementations of them. If we need to use them, we can just add our code there. However, we don’t need either of these stub implementations for this project, so all they’re doing is taking up space and making our code harder to read. We’re going to do our future selves a favor and clear away methods that we don’t need, so go ahead and delete those methods.

At the top of the file, you’ll also see an empty class extension ready for us to use. A class extension is a special kind of Objective-C category declaration that lets you declare methods and properties that will only be usable within a class’s primary implementation block, within the same file. We’ll use the class extension to house our view controller’s outlets. This is a typical pattern that you’ll see often in iOS applications. We put the outlets in the class extension because they don’t need to be visible to code outside the view controller. By contrast, properties and methods that need to be used by other classes would be declared in ViewController.h When you’re finished, your implementation should look like this:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@end

That’s much simpler, huh? Don’t worry about those methods you just deleted. You’ll find out what they do and how to use them in the rest of the book.

Designing the User Interface

Make sure you save the changes you just made, and then single-click Main.storyboard to open your application’s view in Xcode’s Interface Builder (see Figure 3-4). As you’ll remember from the previous chapter, the white window that shows up in the editor represents your application’s one and only view. If you look back at Figure 3-1, you can see that we need to add two buttons and a label to this view.

image

Figure 3-4. Main.storyboard open for editing in Xcode’s Interface Builder

Let’s take a second to think about our application. We’re going to add two buttons and a label to our user interface, and that process is very similar to what we did in the previous chapter. However, we’re also going to need outlets and actions to make our application interactive.

The buttons will need to each trigger an action method on our controller. We could choose to make each button call a different action method; but since they’re going to do essentially the same task (update the label’s text), we will need to call the same action method. We’ll differentiate between the two buttons using that sender argument we discussed earlier in the section on actions. In addition to the action method, we’ll also need an outlet connected to the label so that we can change the text that the label displays.

Let’s add the buttons first and then place the label. We’ll create the corresponding actions and outlets as we design our interface. We could also manually declare our actions and outlets, and then connect our user interface items to them, but why do extra work when Xcode will do it for us?

Adding the Buttons and Action Method

Our first order of business is to add two buttons to our user interface. We’ll then have Xcode create an empty action method for us, and we will connect both buttons to it. Any code we place in that method will be executed when the user taps the button.

Select View image Utilities image Show Object Library or press ^imageimage3 to open the object library. Type UIButton into the object library’s search box (you actually need to type only the first four characters, uibu, to narrow down the list—and you can use all lowercase letters to save yourself the trouble of pressing the Shift key). Once you’re finished typing, only one item should appear in the object library: Button (see Figure 3-5).

image

Figure 3-5. The Button as it appears in the object library

Drag the Button from the library and drop it on the white window inside the editing area. This will add a button to your application’s view. Place the button along the left side of the view the appropriate distance from the left edge by using the blue guidelines that appear to place it. For vertical placement, use the blue guidelines to place the button halfway down in the view. You can use Figure 3-1 as a placement guide, if that helps.

Note The little blue guidelines that appear as you move objects around in Interface Builder are there to help you stick to the iOS Human Interface Guidelines (usually referred to as the HIG). Apple provides the HIG for people designing iPhone and iPad applications. The HIG tells you how you should—and shouldn’t—design your user interface. You really should read it because it contains valuable information that every iOS developer needs to know. You’ll find it athttp://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/.

Double-click the newly added button. This will allow you to edit the button’s title. Give this button a title of Left.

Now, it’s time for some Xcode magic. Select View image Assistant Editor image Show Assistant Editor, or press imageimageimage to open the Assistant Editor. You can also show and hide the Assistant Editor by clicking the middle editor button in the collection of seven buttons on the upper-right side of the project window (see Figure 3-6).

image

Figure 3-6. The Show the Assistant Editor toggle button

The Assistant Editor will appear to the right of the editing pane. The left side will continue to show Interface Builder, but the right will display either ViewController.h or ViewController.m, which are the header and implementation files for the view controller that “owns” the view you’re looking at.

Tip After opening the Assistant Editor, you may need to resize your window to have enough room to work. If you’re on a smaller screen, like the one on a MacBook Air, you might need to close the Utility View and/or Project Navigator to give yourself enough room to use the Assistant Editor effectively. You can do this easily using the three view buttons in the upper right of the project window (see Figure 3-6).

Xcode knows that our view controller class is responsible for displaying the view in the storyboard, and so the Assistant Editor knows to show us the implementation of the view controller class, which is the most likely place we’ll want to connect actions and outlets. However, if it is not displaying the file that you want to see, you can use the jump bar at the top of the Assistant Editor to fix that. First, locate the segment of the jump bar that says Automatic and click it. In the pop-up menu that appears, select Manual image Button Fun image Button Fun image ViewController.m. You should now be looking at the correct file.

As you saw earlier, there’s really not much in the ViewController class. It’s just an empty UIViewController subclass. But it won’t be an empty subclass for long!

We’re now going to ask Xcode to automatically create a new action method for us and associate that action with the button we just created. We’re going to add these definitions to the view controller’s class extension. To do this, begin by clicking the button that you added to the storyboard so that it is selected. Now, hold down the Control key on your keyboard, and then click-and-drag from the button over to the source code in the Assistant Editor. You should see a blue line running from the button to your cursor (see Figure 3-7). This blue line is how we connect objects in IB to code or other objects.

image

Figure 3-7. Control-dragging to source code will give you the option to create an action

If you move your cursor so it’s in the class implementation (between the @implementation and @end keywords—see Figure 3-7), a gray pop-up will appear, letting you know that releasing the mouse button will insert an action for you.

Note We use actions and outlets in this chapter and we’ll use outlet collections later in the book. Outlet collections allow you to connect multiple objects of the same kind to a single NSArray property, rather than creating a separate property for each object.

To finish this connection, release your mouse button, and a floating pop-up will appear, like the one shown in Figure 3-8. This window lets you customize your new action.

image

Figure 3-8. The floating pop-up that appears after you Control-drag to source code

In the Name field, type buttonPressed. When you’re finished, do not hit the Return key. Pressing Return would finalize our outlet, and we’re not quite ready to do that. Instead, press the Tab key to move to the Type field and type in UIButton, replacing the default value of id.

Note As you probably remember, an id is a generic pointer that can point to any Objective-C object. We could leave this as id, and it would work fine; but if we change it to the class we expect to call the method, the compiler can warn us if we try to do this from the wrong type of object. There are times when you’ll want the flexibility to be able to call the same action method from different types of controls, and in those cases, you would want to leave this set to id. In our case, we’re only going to call this method from buttons, so we’re letting Xcode and the compiler know that. Now, they can warn us if we unintentionally try to connect something else to it.

There are two fields below Type, which we will leave at their default values. The Event field lets you specify when the method is called. The default value of Touch Up Inside fires when the user lifts a finger off the screen if—and only if—the finger is still on the button. This is the standard event to use for buttons. This gives the user a chance to reconsider. If the user moves a finger off the button before lifting it off the screen, the method won’t fire.

The Arguments field lets you choose between the three different method signatures that can be used for action methods. We want the sender argument, so that we can tell which button called the method. That’s the default, so we just leave it as is. At this point, the pop-up should look like the one in Figure 3-9.

image

Figure 3-9. The completed Action pop-up

Hit the Return key or click the Connect button, and Xcode will insert the action method for you. The ViewController.m file should now look like this:

@interface ViewController ()

@end

@implementation ViewController

- (IBAction)buttonPressed:(UIButton *)sender {
}

@end

In a few moments, we’ll come back here to write the code that needs to run when the user taps either button.

In addition to creating the method stub, Xcode has also connected that button to that method and stored that information in the storyboard. That means we don’t need to do anything else to make that button call this method when our application runs.

Go back to Main.storyboard and drag out another button, this time placing the button on the right side of the screen. The blue guidelines will appear to help you align it with the right margin, as you saw before, and they will also help you align the button vertically with the other button. After placing the button, double-click it and change its name to Right.

Tip Instead of dragging out a new object from the library, you could hold down the image key (the Option key) drag out a copy of the original object (the Left button in this example) over. Holding down the image key tells Interface Builder to make a copy of the object you drag.

This time, we don’t want to create a new action method. Instead, we want to connect this button to the existing one that Xcode created for us a moment ago. How do we do that? We do it pretty much the same way as we did for the first button.

After changing the name of the button, Control-click it and drag toward the code in the Assistant Editor. Drag toward the declaration of the buttonPressed: method. This time, as your cursor gets near buttonPressed:, that method should highlight, and you’ll get a gray pop-up saying Connect Action (see Figure 3-10). When you see that pop-up, release the mouse button, and Xcode will connect the button to the action method. That will cause the button, when tapped, to trigger the same action method as the other button.

image

Figure 3-10. Dragging to an existing action will connect the button to an existing action

Adding the Label and Outlet

In the object library, type Label into the search field to find the Label user interface item (see Figure 3-11). Drag the Label to your user interface, somewhere above the two buttons you placed earlier. After placing it, use the resize handles to stretch the label from the left margin to the right margin. That should give it plenty of room for the text we’ll be displaying to the user.

image

Figure 3-11. The label as it appears in the object library

The text in a label, by default, is left-aligned, but we want the text in this one to be centered. Select View image Utilities image Show Attributes Inspector (or press imageimage4) to bring up the Attributes Inspector (see Figure 3-12). Make sure the label is selected, and then look in the Attributes Inspector for the Alignment buttons. Select the middle Alignment button to center the label’s text.

image

Figure 3-12. The Attributes Inspector for the label

Before the user taps a button, we don’t want the label to say anything, so double-click the label (so the text is selected) and press the Delete button on your keyboard. That will delete the text currently assigned to the label. Hit Return to commit your changes. Even though you won’t be able to see the label when it’s not selected, don’t worry—it’s still there.

Tip If you have invisible user interface elements, like empty labels, and want to be able to see where they are, select Canvas from the Editor menu. Next, from the submenu that pops up, turn on Show Bounds Rectangles.

All that’s left is to create an outlet for the label. We do this exactly the way we created and connected actions earlier. Make sure the Assistant Editor is open and displaying ViewController.m. If you need to switch files, use the pop-up in the jump bar above the Assistant Editor.

Next, select the label in Interface Builder and Control-drag from the label to the header file. Drag until your cursor is right above the existing action method. When you see something like Figure 3-13, let go of the mouse button, and you’ll see a pop-up window that offers to create an Outlet or an Outlet Collection.

image

Figure 3-13. Control-dragging to create an outlet

We want to create an outlet, so leave the Connection at the default type of Outlet. We want to choose a descriptive name for this outlet so we’ll remember what it is used for when we’re working on our code. Type statusLabel into the Name field. Leave the Type field set to UILabel. The final field, labeled Storage, can be left at the default value.

Hit Return to commit your changes, and Xcode will insert the outlet property into your code. Your class extension should now look like this:

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *statusLabel;

@end

Now we have an outlet, and Xcode has automagically connected the label to our outlet. This means that if we make any changes to statusLabel in code, those changes will affect the label on our user interface. If we set the text property on statusLabel, for example, it will change what text is displayed to the user.

AUTOMATIC REFERENCE COUNTING

If you’re already familiar with Objective-C, or if you’ve read earlier versions of this book, you might have noticed that we don’t have a dealloc method. We’re not releasing our instance variables!

Warning! Warning! Danger, Will Robinson!

Actually, Will, you can relax. We’re quite OK. There’s no danger at all—really.

It’s no longer necessary to release objects. Well, that’s not entirely true. It is necessary, but the LLVM compiler that Apple includes with Xcode these days is so smart that it will release objects for us, using a new feature called Automatic Reference Counting, or ARC, to do the heavy lifting. That means less frequent use of dealloc methods and no more worrying about calling release or autorelease. ARC is such a big improvement that we’re using it for all examples in this book. ARC has been an option in Xcode for the past couple of years, but now it’s enabled by default for each new project you create.

ARC applies only to Objective-C objects, not to Core Foundation objects or to memory allocated with malloc() and the like, and there are some caveats and gotchas that can trip you up. But for the most part, worrying about memory management is a thing of the past.

To learn more about ARC, check out the ARC release notes at this URL:

http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/

ARC is very cool, but it’s not magic. You should still understand the basic rules of memory management in Objective-C to avoid getting in trouble with ARC. To brush up on the Objective-C memory management contract, read Apple’s Memory Management Programming Guide at this URL:

http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/

Writing the Action Method

So far, we’ve designed our user interface and wired up both outlets and actions. All that’s left to do is to use those actions and outlets to set the text of the label when a button is pressed. Single-click ViewController.m in the Project Navigator to open it in the editor and find the emptybuttonPressed: method that Xcode created for us earlier.

To differentiate between the two buttons, we’re going to use the sender parameter. We’ll retrieve the title of the button that was pressed using sender, and then create a new string based on that title and assign that as the label’s text. Add the following bold code to your empty method:

- (IBAction)buttonPressed:(UIButton *)sender {
NSString *title = [sender titleForState:UIControlStateNormal];
NSString *plainText = [NSString stringWithFormat:@"%@ button pressed.", title];
_statusLabel.text = plainText;
}

This is pretty straightforward. The first line retrieves the tapped button’s title using sender. Since buttons can have different titles depending on their current state (although not in this example), we use the UIControlStateNormal parameter to specify that we want the title when the button is in its normal, untapped state. This is usually the state you want to specify when asking a control (a button is a type of control) for its title. We’ll look at control states in more detail in Chapter 4.

The next line creates a new string by appending this text to the title we retrieved in the previous line: “button pressed.” So, if the left button, which has a title of Left, is tapped, this line will create a string that says, “Left button pressed.” The final line assigns the new string to the label’s textproperty, which is how we change the text that the label is displaying.

MESSAGE NESTING

Objective-C messages are often nested. You may come across code like this in your travels:

NSString *plainText = [NSString stringWithFormat:@"%@ button
pressed.",
[sender titleForState:UIControlStateNormal]];

This one (logical) line of code will function exactly the same as the first two lines of our buttonPressed: method. This is because Objective-C methods can be nested, which essentially substitutes the return value from the nested method call.

For the sake of clarity, we won’t generally nest Objective-C messages in the code examples in this book, with the exception of calls to alloc and init, which, by long-standing convention, are almost always nested.

Trying It Out

Guess what? We’re almost finished. Are you ready to try out our app? Let’s do it!

Select Product image Run. If you run into any compile or link errors, go back and compare your code changes to those shown in this chapter. Once your code builds properly, Xcode will launch the iOS simulator and run your application. If you run with an iPhone simulator and tap the Leftbutton, you’ll see something like Figure 3-14.

image

Figure 3-14. Running the application—the layout needs to be fixed

The left button is in the right place, but the label and the other button are not. In Chapter 2, we fixed a similar problem with the Hello World label by switching off Size Classes in the storyboard, which made the design area take on the shape of an iPhone, and then we repositioned the label and tried again. That solution works if you only want to run your application on an iPhone screen of the same size as the one in the storyboard. It doesn’t work if you want to support both the iPhone and the iPad—it doesn’t even work on all iPhones. Fortunately, there is a better way to approach this problem. Instead of changing the layout to fit the screen size shown in Interface Builder, we’ll arrange for it to adapt to the screen that the application is running on, by using Auto Layout. The idea behind Auto Layout is that you use constraints to specify how you want your controls to be placed. In this case, here’s what we want to happen:

· The Left button should be vertically centered and close to the left margin of the screen.

· The Right button should be vertically centered and close to the right margin of the screen.

· The label should be horizontally centered, some way down from the top of the screen.

Each of the preceding statements contains two constraints—one of them a horizontal constraint, the other a vertical constraint. If we apply these constraints to our three views, Auto Layout will take care of positioning them correctly on any screen. So how do we do that?

You can add Auto Layout constraints to views in code by creating instances of the NSLayoutConstraint class. In some cases, that’s the only way to create a correct layout, but in this case (and in most cases), you can get the layout that you want by using Interface Builder. Interface Builder lets you add constraints visually by dragging and clicking. Let’s see how that works.

We’ll start by positioning the label. Select Main.storyboard in the Project Navigator and open the Document Outline to show the view hierarchy. Find the icon labeled View. This represents the view controller’s main view and it’s the one relative to which we need to position the other views. Click the disclosure triangle to open the View icon if it’s not already open, and reveal the two buttons (labeled Left and Right) and the label. Hold down the Control key and drag the mouse from the label to its parent view, as shown on the left in Figure 3-15.

image

Figure 3-15. Positioning the label with Auto Layout constraints

By dragging from one view to another, you are telling Interface Builder that you want to apply an Auto Layout constraint between them. Release the mouse and a gray pop-up with various choices will appear, as shown on the right in Figure 3-15. Each choice in this pop-up is a single constraint. Clicking any of them will apply that constraint, but we know that we need to apply two constraints to the label and both of them are available in the pop-up. To apply more than one constraint at a time, you need to hold down the Shift key while selecting them. So hold down theShift key and click Center Horizontally in Container and on Top Space to Top Layout Guide. To actually apply the constraints, click the mouse anywhere outside the pop-up. When you do this, the constraints that you have created appear under the heading Constraints in the Document Outline and are also represented visually in the storyboard, as shown in Figure 3-16.

image

Figure 3-16. Two Auto Layout constraints have been applied to the label

Tip If you make a mistake when adding a constraint, you can remove it by clicking its representation in the Document Outline, or on the storyboard, and pressing Delete.

The two vertical blue lines represent the constraints that you added—the longer one is the constraint that keeps the label horizontally centered, the shorter one shows that it will be placed a fixed distance below the top of the view.

Tip To see the details for any constraint, select it in the storyboard or the Document Outline and open the Attributes Inspector.

You’ll probably also see that the label has an orange outline. Interface Builder uses orange to indicate an Auto Layout problem. There are three typical problems that Interface Builder highlights in this way:

· You don’t have enough constraints to fully specify a view’s position or size.

· The view has constraints that are ambiguous—that is, they don’t uniquely pin down its size or position.

· The constraints are correct, but the position and/or size of the view at runtime will not be the same as it is in Interface Builder.

You can find out more about the problem by clicking the yellow warning triangle in the Activity View to see an explanation in the Issue Navigator. If you do that, you’ll see that it says “Frame for ‘Label’ will be different at run time”—the third of the problems listed. You can clear this warning by having Interface Builder move the label to its correct runtime position and give it its configured size. To do that, look at the bottom-right side of the storyboard editor. You’ll see four buttons, as shown in Figure 3-17.

image

Figure 3-17. Auto Layout buttons at the bottom right of the storyboard editor

You can find out what each of these buttons does by hovering your mouse over them. Working from left to right, here’s what they are:

· The Align button lets you align the selected view relative to another view. If you click this button now, you’ll see a pop-up that contains various alignment options. One of them is Horizontal Center in Container, a constraint that you have already applied to the label from the Document Outline. There is often more than one way to most Auto Layout-related things in Interface Builder. As you progress through this book, you’ll see alternate ways to do the most common Auto Layout tasks.

· The pop-up for the Pin button contains controls that let you set the position of a view relative to other views and to apply size constraints. For example, you can set a constraint that constrains the height of one view to be the same as that of another.

· The Resolve Auto Layout Issues button lets you correct layout problems. You can use menu items in its pop-up to have Interface Builder remove all constraints for a view (or the entire storyboard), guess at what constraints might be missing and add them, and adjust the frames of one or more views to what they will be at runtime.

· Finally, the Resizing Behavior button lets you control the effect of view resizing on existing constraints.

You can fix the label’s frame by selecting it in the Document Outline or the storyboard and clicking the Resolve Auto Layout Issues button. The pop-up for this button has two identical groups of operations—see Figure 3-18.

image

Figure 3-18. The pop-up for the Resolve Auto Layout Issues button

If you select an operation from the top group, it’s applied only to the currently selected view, whereas operations from the bottom group are applied to all of the views in the view controller. In this case, we just need to fix the frame for one label, so click Update Frames in the top part of the pop-up. When you do this, both the orange outline and the warning triangle in the Activity View disappear, because the label now has the position and size that it will have at runtime. In fact, the label has shrunk to zero width and it’s represented in the storyboard by a small, empty square, as shown in Figure 3-19.

image

Figure 3-19. After fixing its frame, the label has shrunk to zero size

Can this be correct? Well, it turns out that it is correct. Many of the views that UIKit provides, including UILabel, are capable of having Auto Layout set their size based on their actual content. They do this by calculating their natural or intrinsic content size. At its intrinsic size, the label is just wide enough and tall enough to completely surround the text that it contains. At the moment, this label has no content, so its intrinsic content size really should be zero along both axes. When we run the application and click one of the buttons, the label’s text will be set and its intrinsic content size will change. When that happens, Auto Layout will resize the label automatically so that you can see all of the text. Neat, huh?

Tip You can ensure that Auto Layout gives a view its intrinsic content size by selecting it and then clicking Editor image Size to Fit Content in Xcode’s menu.

We’ve taken care of the label, now let’s fix the positions of the two buttons. You could use the same technique of Control-dragging from a button to its parent view and applying constraints from the pop-up that appears when you release the mouse, but I am going to take the opportunity to show you another way. Select the Left button on the storyboard and click the Align button at the bottom right of the storyboard editor (the leftmost button in Figure 3-17). We want the button to be vertically centered, so select Vertical Center in Container in the pop-up and then click Add 1 Constraint (see Figure 3-20).

image

Figure 3-20. Using the Align pop-up to vertically center a view

We need to apply the same constraint to the Right button, so select it and repeat the process. While you were doing this, Interface Builder found a couple of new issues, indicated by the orange outlines in the storyboard and the warning triangle in the Activity View. Click the triangle to see the reasons for the warnings in the Issue Navigator, shown in Figure 3-21.

image

Figure 3-21. Interface Builder warnings for missing constraints

Interface Builder is warning you that the horizontal positions of both buttons are ambiguous. In fact, you haven’t yet set any constraint to control the buttons’ horizontal positions, so this is not surprising.

Note While setting Auto Layout constraints, it is normal for warnings like this to appear and you should use them to help you set a complete set of constraints. You should have no warnings once you have completed the layout process. Most of the examples in this book have instructions for setting layout constraints. While you are adding those constraints, you will usually encounter warnings, but don’t be concerned unless you still have warnings when you have completed all of the steps. In that case, either you missed a step, you performed a step incorrectly, or there is a bug in the book! In the latter case, please let us know by submitting an erratum on the book’s page at http://apress.com.

Let’s fix those warnings. We want the Left button to be a fixed distance from the left side of its parent view and the Right button to be the same distance from the right side of that view. We can set those constraints from the pop-up for the Pin button (the one next to the Align button in Figure 3-17). Select the Left button and click the Pin button to open its pop-up. At the top of the pop-up, you’ll find four input fields connected to a small square by orange dashed lines, as shown on the left in Figure 3-22.

image

Figure 3-22. Using the Pin pop-up to set the horizontal position of a view

The small square represents the button that we are constraining. The four input fields let you set the distances between the button and its nearest neighbors above it, below it, to its left and to its right. A dashed line indicates that no constraint yet exists. In the case of the Left button, we want to set a fixed distance between it and the left side of its parent view, so click the dashed orange line to the left of the square. When you do this, it becomes a solid orange line indicating that there is now a constraint to apply. Next, enter 32 in the left input field to set the distance from the Leftbutton to its parent view. The pop-up should now be as shown on the right in Figure 3-22. Press Add 1 Constraint to apply the constraint to the button.

To fix the position of the Right button, select it, press the Pin button, click the orange dashed line to the right of the square (since we are pinning this button to the right side of its parent view), enter 32 in the input field, and press Add 1 Constraint.

We have now applied all of the constraints that we need, but there may still be warnings in the Activity View. If you investigate, you’ll see that the warnings are because the buttons are not in their correct runtime locations. To fix that, we’ll use the Resolve Auto Layout Issues button again. Click the button to open its pop-up and then click Update Frames from the bottom group of options. We use the option from the bottom group because we need the frames of all of the views in the view controller to be adjusted.

Tip You may find that none of the options in the pop-up menu is available. If this is the case, select the View Controller icon in the Document Outline and try again.

The warnings should now go away and our layout is finally complete. Run the application on an iPhone simulator and you’ll see a result that’s almost like Figure 3-1 at the beginning of this chapter. When you tap the right button, this text should appear: “Right button pressed.” If you then tap the left button, the label will change to say, “Left button pressed.” So far, so good. But if you look back at Figure 3-1, you’ll see that one thing is missing. The screenshot we showed you for our end result displays the name of the chosen button in bold text; however, what we’ve made just shows a plain string. We’ll bring on the boldness using the NSAttributedString class in just a second. First, let’s take the opportunity to look at another useful feature of Xcode—layout previews.

Previewing Layout

Return to Xcode and select Main.storyboard, and then open the Assistant Editor if it’s not already showing (refer back to Figure 3-6 if you need a reminder of how to do this.) At the left of the jump bar at the top of the Assistant Editor, you’ll see that the current selection is Automatic (unless you changed it to Manual to select the file for the Assistant Editor to display). Click to open the pop-up for this segment of the jump bar and you’ll see several options, the last of which is Preview. When you hover the mouse over Preview, a menu containing the name of the application’s storyboard will appear. Click it to open the storyboard in the Preview Editor.

When the Preview Editor opens, you’ll see the application as it appears on an iPhone in portrait mode. This is just a preview, so it won’t respond to button clicks, and as a result, you won’t see the label. If you move your mouse over the area just below the preview, where it says iPhone 4-inch, a control will appear that will let you rotate the phone into landscape mode. You can see the control on the left of Figure 3-23, and the result of clicking it to rotate the phone on the right.

image

Figure 3-23. Previewing the layout on the iPhone in portrait (left) and landscape (right) modes

Thanks to Auto Layout, when the phone rotates, the buttons move so that they remain vertically centered and the same distance away from the sides of the device, as in portrait orientation. If the label were visible, you would see that it is in the correct position too.

We can also use the Preview Assistant to see what happens when we run the application on a different device. At the bottom left of the Preview Assistant (and in Figure 3-23), you’ll see a + icon. Click this to open a list of devices and then select iPad to add an iPad preview to the Preview Assistant. The iPad preview takes up a lot of space, so you may need to close the Document Outline and the Utility View to make enough room to see both the iPhone and iPad. If you still can’t see the whole iPad screen, you can zoom the Preview Assistant in a couple of different ways. The easiest is to double-click the Preview Assistant pane—this toggles between a full size view and a much smaller view. If you’d like more control over the zoom level, you can use a pinch gesture on your trackpad (unfortunately, this is not supported on the magic mouse, at least not at the time of writing). Figure 3-24 shows the iPhone and iPad previews, zoomed out to fit in the space available on my screen. Once again, Auto Layout has arranged for the buttons to be in the correct locations. Rotate the iPad preview to see that the layout also works in iPad landscape mode.

image

Figure 3-24. Previewing the layout on an iPhone and an iPad at the same time

Using the Preview Assistant can save you a lot of time when building and debugging a layout. You can see how the layout works on more than one device and in both orientations. In fact, you can add another pair of iPhone and iPad previews and rotate them to landscape if you want to see the layout in both orientations on both devices at the same time. The best thing of all is that the preview is live—if you make changes to the layout, the preview updates too!

To see this, go back to the storyboard editor, select one of the buttons and drag it toward the other one, and then release it. Now make sure the button that you dragged is selected in the Document Outline, open the Resolve Auto Layout Issues pop-up, and click Update Constraints. This tells Interface Builder to change the Auto Layout constraints so that the adjustment you just made to the button becomes permanent. When you do this, you’ll see that the button jumps immediately to the new location in both devices in the Preview Assistant. Neat, huh?

Let’s move on to adding some boldness to the label’s text. Before we do that, though, we need to put that button back where it belongs. You could drag it back into position and adjust its constraints again, but there’s a quicker way—just press imageZ twice to undo your last change and all should be well.

Adding Some style

The NSAttributedString class lets you attach formatting information, such as fonts and paragraph alignment, to a string. This metadata can be applied to an entire string, or different attributes can be applied to different parts. If you think about the ways that formatting can be applied to pieces of text in a word processor, that’s basically the model for how NSAttributedString works.

However, until recently, none of the Apple-provided UIKit classes have been able to do anything with attributed strings. If you wanted to present a label containing both bold text and normal text, you’d have to either use two labels or draw the text directly into a view on your own. Those approaches aren’t insurmountable hurdles, but they’re tricky enough that most developers would rather not follow those paths too often. iOS 6 brought many improvements for anyone who wants to display styled text, since most of the main UIKit controls now let you use attributed strings. In the case of a UILabel like the one we have here, it’s as simple as creating an attributed string, and then passing it to the label via its attributedText property.

So, select ViewController.m and update the buttonPressed: method by deleting the crossed-out line and adding the bold lines shown in this snippet:

- (IBAction)buttonPressed:(UIButton *)sender {
NSString *title = [sender titleForState:UIControlStateNormal];
NSString *plainText = [NSString stringWithFormat:@"%@ button pressed.", title];
_statusLabel.text = plainText;

NSMutableAttributedString *styledText = [[NSMutableAttributedString alloc]
initWithString:plainText];
NSDictionary *attributes =
@{
NSFontAttributeName : [UIFont boldSystemFontOfSize:_statusLabel.font.pointSize]
};

NSRange nameRange = [plainText rangeOfString:title];

[styledText setAttributes:attributes range:nameRange];
_statusLabel.attributedText = styledText;
}

The first thing that new code does is create an attributed string—specifically, an NSMutableAttributedString instance—based on the string we are going to display. We need a mutable attributed string here because we want to change its attributes.

Next, we create a dictionary to hold the attributes we want to apply to our string. Really, we have just one attribute right now, so this dictionary contains a single key-value pair. The key, NSFontAttributeName, lets you specify a font for a portion of an attributed string. The value we pass in is something called the bold system font, which is specified to be the same size as the font currently used by the label. Specifying the font this way is more flexible in the long run than specifying a font by name, since we know that the system will always have a reasonable idea of what to use for a bold font.

Tip If you’ve been using Objective-C for a while, you may not be familiar with this new dictionary syntax—but it’s pretty simple. Instead of requiring an explicit call to a class method on NSDictionary, the version of LLVM included with Xcode provides a shorthand form, which is nicer to use. It basically looks like this:

@{ key1 : value1, key2 : value2 }

Apart from eliminating the need to type the same lengthy class name and method name every time you want to make a dictionary, it also puts the keys and values in the “right order”—at least according to anyone who’s ever used a language with built-in dictionaries, such as Ruby, Python, Perl, or JavaScript.

This new dictionary syntax was introduced in 2012, along with similar syntax for arrays and numbers. We’ll be using these new pieces of syntax throughout the book.

Next, we ask our plainText string to give us the range (consisting of a start index and a length) of the substring where our title is found. We use the range to apply the attributes to the part of the attributed string that corresponds to the title and pass it off to the label.

Now you can hit the Run button, and you’ll see that the app shows the name of the clicked button in bold text, as shown in Figure 3-1.

Looking at the Application Delegate

Well, cool! Your application works! Before we move on to our next topic, let’s take a minute to look through the two source code files we have not yet examined, AppDelegate.h and AppDelegate.m. These files implement our application delegate.

Cocoa Touch makes extensive use of delegates, which are objects that take responsibility for doing certain tasks on behalf of another object. The application delegate lets us do things at certain predefined times on behalf of the UIApplication class. Every iOS application has exactly one instance of UIApplication, which is responsible for the application’s run loop and handles application-level functionality, such as routing input to the appropriate controller class. UIApplication is a standard part of the UIKit, and it does its job mostly behind the scenes, so you generally don’t need to worry about it.

At certain well-defined times during an application’s execution, UIApplication will call specific methods on its delegate, if the delegate exists and implements the method. For example, if you have code that needs to fire just before your program quits, you would implement the methodapplicationWillTerminate: in your application delegate and put your termination code there. This type of delegation allows your application to implement common application-wide behavior without needing to subclass UIApplication or, indeed, without needing to know anything about the inner workings of UIApplication. All of the Xcode templates create an application delegate for you and arrange for it to be linked to the UIApplication object when the application launches.

Click AppDelegate.h in the Project Navigator to see the application delegate’s header file. It should look like this:

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

One thing worth pointing out is this line of code:

@interface AppDelegate : UIResponder <UIApplicationDelegate>

Do you see that value between the angle brackets? This indicates that this class conforms to a protocol called UIApplicationDelegate. Hold down the image key. Your cursor should turn into crosshairs. Move your cursor so that it is over the word UIApplicationDelegate. Your cursor will turn into a question mark, and the word UIApplicationDelegate will be highlighted, as if it were a link in a browser (see Figure 3-25).

image

Figure 3-25. When you hold down the image key (the Option key) in Xcode and point at a symbol in your code, the symbol is highlighted and your cursor changes into a pointing hand with a question mark

With the image key still held down, click this link. This will open a small pop-up window showing a brief overview of the UIApplicationDelegate protocol (see Figure 3-26).

image

Figure 3-26. When we Option-clicked <UIApplicationDelegate> from within our source code, Xcode popped up this window, called the Quick Help panel, which describes the protocol

Notice the two links at the bottom of this new pop-up documentation window (see Figure 3-26). Click the Reference link to view the full documentation for this symbol or click the Declared In link to view the symbol’s definition in a header file. This same trick works with class, protocol, and category names, as well as method names displayed in the editor pane. Just Option-click a word, and Xcode will search for that word in the documentation browser.

Knowing how to look up things quickly in the documentation is definitely worthwhile, but looking at the definition of this protocol is perhaps more important. Here’s where you’ll find which methods the application delegate can implement and when those methods will be called. It’s probably worth your time to read over the descriptions of these methods.

Note If you’ve worked with Objective-C before but not with Objective-C 2.0, you should be aware that protocols can now specify optional methods. UIApplicationDelegate contains many optional methods. However, you do not need to implement any of the optional methods in your application delegate unless you have a reason to do so.

Back in the Project Navigator, click AppDelegate.m to see the implementation of the application delegate. It should look something like this:

#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive
state. This can occur for certain types of temporary interruptions
(such as an incoming phone call or SMS message) or when the user quits
the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data,
invalidate timers, and store enough application state information to
restore your application to its current state in case it is terminated
later.
//
If your application supports background execution, this method is
called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the
inactive state; here you can undo many of the changes made on entering
the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while
the application was inactive. If the application was previously
in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save
data if appropriate. See also applicationDidEnterBackground:.
}

@end

At the top of the file, you can see that our application delegate has implemented one of those protocol methods covered in the documentation, called application:didFinishLaunchingWithOptions:. As you can probably guess, this method fires as soon as the application has finished all the setup work and is ready to start interacting with the user. It is often used to create any objects that need to exist for the entire lifetime of the running app.

You’ll see more of this later in the book, especially in Chapter 15 where we’ll say a lot more about the role that the delegate plays in the application life cycle. We just wanted to give you a bit of background on application delegates and show how this all ties together before closing this chapter.

Bring It on Home

This chapter’s simple application introduced you to MVC, creating and connecting outlets and actions, implementing view controllers, and using application delegates. You learned how to trigger action methods when a button is tapped and saw how to change the text of a label at runtime. Although we built a simple application, the basic concepts we used are the same as those that underlie the use of all controls under iOS, not just buttons. In fact, the way we used buttons and labels in this chapter is pretty much the way that we will implement and interact with most of the standard controls under iOS.

It’s critical that you understand everything we did in this chapter and why we did it. If you don’t, go back and review the parts that you don’t fully understand. This is important stuff! If you don’t make sure you understand everything now, you will only get more confused as we get into creating more complex interfaces later in this book.

In the next chapter, we’ll take a look at some of the other standard iOS controls. You’ll also learn how to use alerts to notify the user of important happenings and how to use action sheets to indicate that the user needs to make a choice before proceeding. When you feel you’re ready to proceed, give yourself a pat on the back for being such an awesome student and head on over to the next chapter.