© Molly Maskrey, Kim Topley, David Mark, Fredrik Olsson and Jeff Lamarche 2016

Molly Maskrey, Kim Topley, David Mark, Fredrik Olsson and JEFF LAMARCHE, Beginning iPhone Development with Swift 3, 10.1007/978-1-4842-2223-2_3

3. Basic User Interactions

Molly Maskrey, Kim Topley2, David Mark3, Fredrik Olsson4 and Jeff Lamarche5

(1)Parker, Colorado, USA

(2)Apt 10G, New York, USA

(3)Arlington, Virginia, USA

(4)Stockholm, Sweden

(5)New Hartford, New York, USA

Our Hello World app provided a good introduction to iOS development using Xcode and Cocoa Touch, but it lacked a crucial capability—the ability to interact with the user. Without the ability to accept user input, we severely limit the usefulness of our efforts.

In this chapter, we’ll write a slightly more complex application—one that will feature two buttons and a label (see Figure 3-1) . When the user taps either of the buttons, the label’s text changes. This demonstrates the key concepts involved in creating interactive iOS apps. We’ll also learn about the NSAttributedString class, which lets you use styled text with many Cocoa Touch visual elements.

A329781_3_En_3_Fig1_HTML.jpg
Figure 3-1. In this chapter, we’ll develop this simple two-button app

The MVC Paradigm

If you don’t know of MVC, you will. It stands for model-view-controller (MVC) and represents a very logical way of dividing the code that makes up a GUI-based application. These days, almost all object-oriented frameworks build upon MVC, but few adhere to the MVC model as much 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.

MVC makes certain that the objects implementing these three types of code are 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 primarily create your view components using Interface Builder, although you will sometimes modify, and sometimes even create, parts of your user interface in code.

You create your model by writing Swift classes to hold your application’s data. We won’t be creating any model objects in this chapter’s application because we do not need to store or preserve data. We will introduce model objects as our applications get more complex in future chapters.

Your controller component contains classes that you create and that are specific to your application. Controllers are completely custom classes, but more often they exist as subclasses of one of several existing generic controller classes from the UIKit framework, such as UIViewController. By subclassing one of these existing classes, you 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, we quickly start to see how the classes of the UIKit framework follow the principles of MVC. Keeping this concept in the back of your mind as you develop and you will end up creating cleaner, more easily maintained Swift code.

Creating the ButtonFun App

It’s time to create our next Xcode project. We’ll use the same template that we used in the previous chapter: Single View Application. By starting with this simple template again, it’s easier 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 ➤ New ➤ Project… or press ⌘N. 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, so you can leave those alone. Once again, 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.

A329781_3_En_3_Fig2_HTML.jpg
Figure 3-2. Naming our project and selecting options

Hit Next. 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.

The ViewController

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. 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).

A329781_3_En_3_Fig3_HTML.jpg
Figure 3-3. The Project Navigator shows the class files created for us by the project template

The Button Fun group should contain two source code files along with the main storyboard file, the launch screen storyboard file, an asset catalog for containing any images our app needs and an Info.plist file, which we’ll discuss in later chapters. The two source code files implement the classes 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 manages our application’s view. The name identifies that this class is, well, a view controller. Click ViewController.swift in the Project Navigator and take a look at the contents of the view controller file (see Listing 3-1).

Listing 3-1. ViewController Code Generated by Our Template
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

Because it was generated by the template, clearly there’s not much there yet. ViewController exists as a subclass of UIViewController, which is one of those generic controller classes mentioned earlier. It is part of the UIKit framework, and by subclassing this class, we get a bunch of functionality included. 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 specific functionality ourselves.

Outlets and Actions

In Chapter 2 we used Xcode’s Interface Builder to design a simple user interface and in listing 3-1 we saw the shell of a view controller class. We’ll now discuss how the code in this view controller class can interact with the objects (buttons, labels, etc.) in the storyboard. 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 in Chapter 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. We’ll do that later 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, but 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. 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 ordinary Swift properties that are tagged with the decoration @IBOutlet. An outlet looks something like this:

@IBOutlet weak var myButton: UIButton!

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

The Swift compiler doesn’t do anything special when it sees the @IBOutlet decoration. 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 @IBOutlet. Fortunately, as you’ll see, you can create outlets in Xcode just by dragging from the object to the property that you want to link it to, or even just by dragging to the class in which you’d like to have a new outlet created.

You may be wondering why the declaration of the myButton property ends with an !. Swift requires all properties to be fully initialized before the completion of every initializer, unless the property is declared to be optional. When a view controller loads from a storyboard, the values of its outlet properties get set from information saved in the storyboard, but this happens after the view controller’s initializer has been run. As a result, unless you explicitly give them dummy values (which is not desirable), outlet properties have to be declared as optional. That gives you two ways to declare them, using either ! or ?, as shown in Listing 3-2.

Listing 3-2. Two Ways to Declare Optional Variables
@IBOutlet weak var myButton1: UIButton?
@IBOutlet weak var myButton2: UIButton!

Generally, you’ll find the second one easier to use, because there is no need to explicitly unwrap the optional later when it’s used in the view controller’s code (see Listing 3-3). Be aware that if you do use the second version, you must make sure that it gets set and does not become nil later.

Listing 3-3. Eliminating the Need to Explicitly Unwrap an Optional
let button1 = myButton1!   // Optional needs to be unwrapped                  
let button2 = myButton2    // myButton2 is implicitly unwrapped
Note

The weak specifier attached to the declaration of the outlet property means that the property does not need to create a strong reference to the button. Objects are automatically deallocated as soon as there are no more strong references to them. In this case, there is no risk that the button will be deallocated because there will be a strong reference to it as long as it remains part of the user interface. Making the property reference weak allows deallocation to happen if the view is no longer required and is removed from the user interface at some point. If this happens, the property reference is set to nil.

Actions

In a nutshell, actions are methods that are tagged with the decoration @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 func doSomething(sender: UIButton) {}

It might also look like this:

@IBAction func doSomething() {}

The actual name of the method can be anything you want and it 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 giving you a way to identify which control called the action method.

Tip

There’s actually a third, less frequently used way to declare an action method that looks like this:

@IBAction func doSomething(sender: UIButton,
                           forEvent event: UIEvent) {}

You would use this form if you need more information about the event that caused the method to be called. We’ll talk more about control events in the next chapter.

It won’t hurt anything to declare an action method with a sender argument and then ignore it. You’ll 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, we’ll see how they work as we design our user interface. But first, let’s take care of a little bit of housekeeping.

Simplifying the View Controller

Single-click ViewController.swift 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 ourselves a favor and clear away methods that we don’t need, so go ahead and delete both of them. When you’ve done that, your file should look like Listing 3-4.

Listing 3-4. Our Simplified ViewController.swift File
import UIKit

class ViewController: UIViewController {

}

Designing the User Interface

Make sure that you save the changes you just made, and then single-click Main.storyboard to open your application’s view using 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.

A329781_3_En_3_Fig4_HTML.jpg
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 to add a label to the application that we built in the previous chapter. However, we’re also going to need outlets and actions to make our application interactive.

The buttons will each need to 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 discussed earlier. 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.

We’ll add the buttons first and then place the label, creating 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 Xcode can handle this for us.

Adding the Buttons and Action Method

First, we’ll 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 ➤ Utilities ➤ Show Object Library or press ^⌥⌘3 to open the object library. Type UIButton in the object library’s search box. (You actually need to type only the first four characters, uibu, to narrow down the list. 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).

A329781_3_En_3_Fig5_HTML.jpg
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 to add the 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 vertical blue guideline that appears as you move the button toward the left edge of the view. For vertical placement, use the horizontal blue guideline 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 (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 at https://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 the title Left.

Select View ➤ Assistant Editor ➤ Show Assistant Editor, or press ⌥⌘ 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).

A329781_3_En_3_Fig6_HTML.jpg
Figure 3-6. The Show the Assistant Editor toggle button (double circles)

The Assistant Editor appears to the right of the editing pane, which continues to show Interface Builder. The Assistant Editor should automatically display ViewController.swift, which is the implementation file 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 (see Figure 3-7). You can do this easily using the three view buttons in the upper right of the project window (see Figure 3-6).

A329781_3_En_3_Fig7_HTML.jpg
Figure 3-7. You may have to close other views to see both editing windows on smaller displays

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 ➤ Button Fun ➤ Button Fun ➤ ViewController.swift. You should now be looking at the correct file.

We’ll now let Xcode 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, as shown in Figure 3-8. This blue line allows us to connect objects in IB to code or other objects. Moving your cursor so it’s inside the class definition, as shown in Figure 3-8, a pop-up appears, letting you know that releasing the mouse button will insert an outlet, an action, or an outlet collection for you.

A329781_3_En_3_Fig8_HTML.jpg
Figure 3-8. Control-dragging to source code will give you the option to create an outlet, action, or outlet collection
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 array 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-9.

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

This window lets you customize your new action. In the window, click the pop-up menu labeled Connection and change the selection from Outlet to Action. This tells Xcode that we want to create an action instead of an outlet (see Figure 3-10). In the Name field, type buttonPressed. When you’re finished, do not hit the Return key. Pressing Return would finalize our outlet; we’re not quite ready to do that. Instead, press the Tab key to move to the Type field and type UIButton, replacing the default value of AnyObject.

A329781_3_En_3_Fig10_HTML.jpg
Figure 3-10. Changing from Outlet to Action

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 ifand only ifthe 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.

Hit the Return key or click the Connect button, and Xcode will insert the action method for you. The ViewController.swift file in the Assistant Editor should now look like Listing 3-5. We’ll come back here to write the code that needs to execute when the user taps either this button or the one we’ll add shortly.

Listing 3-5. Our ViewController.swift File with the Added IBAction
import UIKit

class ViewController: UIViewController {

    @IBAction func buttonPressed(_ sender: UIButton) {
    }
}

In addition to creating the method code segment, Xcode 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 ⌥ key (the Option key) drag out a copy of the original object (the Left button in this example) over. Holding down the ⌥ 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. After changing the name of the button, Control-click it and drag toward the declaration of the buttonPressed() method code in the Assistant Editor. 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-11). If you don’t see it straightaway, move the mouse around until it appears. When you see the 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.

A329781_3_En_3_Fig11_HTML.jpg
Figure 3-11. Dragging to an existing action will connect the button to that action

Adding the Label and Outlet

In the object library, type lab into the search field to find the Label user interface item (see Figure 3-12). 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 (as indicated by the blue guideline) to the right margin. That should give it plenty of room for the text we’ll be displaying to the user.

A329781_3_En_3_Fig12_HTML.jpg
Figure 3-12. 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 ➤ Utilities ➤ Show Attributes Inspector (or press ⌥⌘4) to bring up the Attributes Inspector (see Figure 3-13). Make sure that 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.

A329781_3_En_3_Fig13_HTML.jpg
Figure 3-13. Use the attributes inspector to center the label’s text

Before the user taps a button, we want the label blank, 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, 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. If you just want to select an element that you can’t see, just click its icon in the Document Outline.

Finally, let’s create an outlet for the label. We do this exactly the way we created and connected actions earlier. Make sure that the Assistant Editor is open and displaying ViewController.swift. 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-14, let go of the mouse button and you’ll see the pop-up window again (shown in Figure 3-9).

A329781_3_En_3_Fig14_HTML.jpg
Figure 3-14. Connecting the UILabel outlet

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 code should now look like Listing 3-6.

Listing 3-6. Adding Our Label Outlet to the View Controller
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var statusLabel: UILabel!

    @IBAction func buttonPressed(_ sender: UIButton) {
    }
}

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

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.swift in the Project Navigator to open it in the editor. Find the empty buttonPressed() method that Xcode created for us earlier.

To differentiate between the two buttons, we’ll 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. Change the buttonPressed() method to that shown in Listing 3-7.

Listing 3-7. Completing the Action Method
@IBAction func buttonPressed(sender: UIButton) {
    let title = sender.title(for: .selected)!
    let text = "(title) button pressed"
    statusLabel.text = text
}

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 UIControlState.selected parameter to specify that we want the title when the button is in its selected state since the user selected it by tapping it. We’ll look at control states in more detail in Chapter 4.

Tip

You probably noticed that the argument we used to call the title(for: ) method was .selected, not UIControlState.selected. Swift already understands that the argument must be one of the values of the UIControlState enumeration, so we can omit the enumeration name to save typing.

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 reads: Left button pressed. The final line assigns the new string to the label’s text property, which is how we change the text that the label is displaying.

Testing the ButtonFun App

Select Product ➤ 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 Left button, you’ll see something like Figure 3-15.

A329781_3_En_3_Fig15_HTML.jpg
Figure 3-15. Running the application on our iPhone 6s

While everything seems to be good with this, the layout of everything needs some work. To see why, change the active scheme, as shown in Figure 3-16 to an iPhone SE and run the application again.

A329781_3_En_3_Fig16_HTML.jpg
Figure 3-16. Changing the scheme, and thus the target of execution, to a different size and shape

The result, shown in Figure 3-17, shows the problems. Note that while the left button still works, the label itself is off to the right a bit and the right button has completely vanished.

A329781_3_En_3_Fig17_HTML.jpg
Figure 3-17. Our layout is a little off using a different simulated device

To start to see why, in Xcode underneath our Interface Builder window, tap the right button to select it and see the outline, and then below select the View As for iPhone SE, as shown in Figure 3-18. You can see that because we set up our layout to work on a device with a larger screen size, when we move to a smaller device, some of the controls have moved within our new display.

A329781_3_En_3_Fig18_HTML.jpg
Figure 3-18. When viewing our layout on a device with a smaller screen area, our right button shifted so that it is no longer visible

Fixing Issues Using Auto Layout

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 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, a little 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 all of the examples in this book), you can get the layout that you want by using Interface Builder. Interface Builder lets you add constraints visually by dragging and clicking. First, in the View As under the IB window, reselect 6s as our device so that we see all our controls as we had them. Set the scale to where you can see the whole screen; I’m using 75%. We’ll use Auto Layout to fix the other device configurations as well (see Figure 3-19).

A329781_3_En_3_Fig19_HTML.jpg
Figure 3-19. We’ll use Auto Layout with the same device we started with to configure for all other device types

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-20.

A329781_3_En_3_Fig20_HTML.jpg
Figure 3-20. 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-20. 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 the Shift key and click Center Horizontally in Container and Vertical Spacing to Top Layout Guide. To actually apply the constraints, click the mouse anywhere outside the pop-up or press the return key. 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-21.

A329781_3_En_3_Fig21_HTML.jpg
Figure 3-21. 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 in the storyboard, and pressing Delete.

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 (see Figure 3-21, far left). If you do that, you’ll see that it says Frame for “Status 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-22.

A329781_3_En_3_Fig22_HTML.jpg
Figure 3-22. 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. The left button relates to the UIStackView control, which we’ll talk about in Chapter 10. Working from left to right, here’s what the other three buttons are:

  1. 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 achieve 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.

  2. 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.

  3. 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 which constraints might be missing, add them, and adjust the frames of one or more views to what they will be at runtime.

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-23).

A329781_3_En_3_Fig23_HTML.jpg
Figure 3-23. The pop-up for the Resolve Auto Layout Issues button
Tip

If none of the items in the pop-up is enabled, click the label in the Document Outline to ensure that it’s selected and try again.

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-24.

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

It turns out that this is actually what we want to see. 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 is set and its intrinsic content size changes. When that happens, Auto Layout will resize the label automatically so that you can see all of the text.

Now that we’ve taken care of the label, we’ll fix the positions of the two buttons. Select the Left button on the storyboard and click the Align button at the bottom right of the storyboard editor (the second button in Figure 3-22, counting from the left). 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-25).

A329781_3_En_3_Fig25_HTML.jpg
Figure 3-25. 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 , as shown in Figure 3-26.

A329781_3_En_3_Fig26_HTML.jpg
Figure 3-26. Interface Builder warnings for missing constraints

Interface Builder warns us that the horizontal positions of both buttons are ambiguous. In fact, since we haven’t yet set any constraint to control the buttons’ horizontal positions, this should not be a surprise.

Note

While setting Auto Layout constraints, it is normal for warnings like this to appear. 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, you missed a step, you performed a step incorrectly, or possibly there is an error in the book. In the latter case, please let us know by submitting an erratum on the book’s page at www.apress.com .

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 to the right of the Align button in Figure 3-22). 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-27. 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 Left button to its parent view.

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

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 (it’s the rightmost one) 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 top group 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 . 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 Left button pressed. Run the example on an iPad simulator and you’ll find that the layout still works, although the buttons are further apart because of the wider screen. That’s the power of auto layout.

Tip

When running the application on simulated devices with large screens, you may find that you can’t see the whole screen at once. You can fix this by selecting Window ➤ Scale in the iOS Simulator menu and choosing a scale that’s appropriate for the screen you’re using.

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. Let’s 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 cursor 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 6s, 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-28, and the result of clicking it to rotate the phone.

A329781_3_En_3_Fig28_HTML.jpg
Figure 3-28. Previewing the layout of the iPhone in landscape mode

Using Auto Layout, when we rotate the phone, 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 as well.

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-28), you’ll see a + icon. Click this to open a list of devices, and then select iPhone SE to add the new preview to the Preview Assistant. If you still can’t see everything, 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-29 shows the two iPhone 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 iPhone SE preview to see that the layout also works in landscape mode.

A329781_3_En_3_Fig29_HTML.jpg
Figure 3-29. Previewing layouts on two devices at the same time
Note

At the time of writing, Preview Assistant did not correctly show iPad 9.7-inch and 12.9-inch preview layouts correctly. This was likely due to bugs in the beta release of Xcode 8. However, running them in the simulator showed them working correctly.

Changing the Text 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. Most of the main UIKit controls 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.swift and update the buttonPressed() method, as shown in Listing 3-8.

Listing 3-8. Updated buttonPressed() Method to Add Bold Font Characteristic
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var statusLabel: UILabel!

    @IBAction func buttonPressed(_ sender: UIButton) {
        let title = sender.title(for: .selected)!
        let text = "(title) button pressed"
        let styledText = NSMutableAttributedString(string: text)
        let attributes = [
            NSFontAttributeName:
                UIFont.boldSystemFont(ofSize: statusLabel.font.pointSize)
        ]
        let nameRange = (text as NSString).range(of: title)
        styledText.setAttributes(attributes, range: nameRange)


        statusLabel.attributedText = styledText

    }
}

The first thing the 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. We have just one attribute right now, so this dictionary contains a single key-value pair. The key, NSFontAttributeName, lets us specify a font for a portion of an attributed string. The value we pass in is the bold system font of 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.

Next, we ask our text 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. Let’s take a closer look at the line that locates the title string:

let nameRange = (text as NSString).range(of: title)

Notice that the text variable is cast from the Swift type String to the Core Foundation type NSString. That’s necessary because both String and NSString have methods called range(of: String). We need to call the NSString method to get the range as an NSRange object, since that’s what the setAttributes() method on the next line expects.

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

Examining the Application Delegate

Now that our application works, before we move on to our next topic, let’s take a minute to look through the source code file we have not yet examined—AppDelegate.swift. This file implements 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; 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 calls specific methods on its delegate, if the delegate exists and implements the method. For example, if you have code that needs to be executed just before your program quits, you would implement the method applicationWillTerminate() in your application delegate and put your termination code there. This type of delegation allows your application to implement 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.swift in the Project Navigator to see the stub application delegate that the project template provides. The first couple of lines should look like that shown in Listing 3-9.

Listing 3-9. Application Delegate Initial Code
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {


    var window: UIWindow?

The code highlighted in bold indicates that this class conforms to a protocol called UIApplicationDelegate. Hold down the ⌥ 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-30).

A329781_3_En_3_Fig30_HTML.jpg
Figure 3-30. When you hold down the ⌥ 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 ⌥ key still held down, click this link. A small pop-up window will open, showing a brief overview of the UIApplicationDelegate protocol (see Figure 3-31).

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

Scrolling the pop-up to the bottom, you’ll find two links (see Figure 3-32).

A329781_3_En_3_Fig32_HTML.jpg
Figure 3-32. Links to additional information about the selected item

Notice the two links at the bottom of this new pop-up documentation window; click the More 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 and protocol names, as well as method names displayed in the editor pane. Just Option-click a word, and Xcode searches 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.

Back in the Project Navigator, return to AppDelegate.swift to see the implementation of the application delegate. It should look something like Listing 3-10.

Listing 3-10. Our AppDelegate.swift File
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {


    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }


    func applicationWillResignActive(_ application: UIApplication) {
        // 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.
    }


    func applicationDidEnterBackground(_ application: UIApplication) {
        // 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.
    }


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


    func applicationDidBecomeActive(_ application: UIApplication) {
        // 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.
    }


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


}

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.

Summary

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 you saw how to change the text of a label at runtime. Although we built a very simple application, the basic concepts 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 to understand everything we did in this chapter and why we did it. If you don’t, make sure to review the parts that you don’t fully understand. If things are not completely clear, 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.86.56