© Molly K. Maskrey 2017

Molly K. Maskrey, Beginning iPhone Development with Swift 4, https://doi.org/10.1007/978-1-4842-3072-5_4

4. Adding Intermediate-Level User Interactions

Molly K. Maskrey

(1)Parker, Colorado, USA

In Chapter 3, I discussed MVC, and you built an application using it. You learned about outlets and actions, and you used them to tie a button to a text label. In this chapter, you’ll build an application with a broader set of controls to increase your familiarity with developing a user interface, like the one shown in Figure 4-1.

A329781_4_En_4_Fig1_HTML.jpg
Figure 4-1. This chapter’s project increases your UI skills by adding several new controls to the mix

You’ll implement an image view, a slider, two different text fields, a segmented control, a couple of switches, and an iOS button that looks like buttons did before iOS 7. You’ll see how to set and retrieve the values of various controls. You’ll learn how to use action sheets to force the user to make a choice and how to use alerts to give the user important feedback. You’ll also learn about control states and the use of stretchable images to change the appearance of buttons.

Because this chapter’s application uses so many different user interface items, you’re going to work a little differently than you did in the previous two chapters. You’ll break your application into pieces, implementing one piece at a time. Bouncing back and forth between Xcode and the iOS simulator, you’ll test each piece before you move on to the next. Dividing the process of building a complex interface into smaller components makes it much less intimidating, as well as more like the actual process you’ll go through when building your own applications. This code-compile-debug cycle makes up a large part of a software developer’s typical day.

Your app uses only a single view and controller, but as you can see in Figure 4-1, there’s a lot more complexity in this one view.

The logo at the top of the screen exists in an image view, which, in this app, does nothing more than display a static image. I placed two text fields below the logo: one allowing the entry of alphanumeric text and the other permitting only numbers. A slider sits just below the text fields. As the user moves the slider, the value of the label next to it changes so that it always reflects the slider’s current value.

Below the slider are a segmented control and two switches. The segmented control toggles between two different types of controls in the space underneath it. When the application first launches, two switches appear below the segmented control. Changing the value of either switch causes the other one to change its value to match. While this isn’t something you would likely do in a real application, it demonstrates how to change the value of a control programmatically and how Cocoa Touch animates certain actions without you needing to do any work.

Figure 4-2 shows what happens when the user taps the segmented control. The switches disappear and are replaced by a button.

A329781_4_En_4_Fig2_HTML.jpg
Figure 4-2. Tapping the segmented controller on the left side causes a pair of switches to be displayed. Tapping the right side causes a button to be displayed, as shown here.

Pressing Do Something causes an action sheet to pop up, asking if the user really meant to tap the button (see Figure 4-3). Notice how the action sheet is now highlighted and in the foreground, while the other controls become dimmed.

A329781_4_En_4_Fig3_HTML.jpg
Figure 4-3. An action sheet requests a response from the user

This provides a standard way of responding to input that is potentially dangerous or that could have significant repercussions, and it gives the user a chance to stop potential problems from happening. If Yes, I’m Sure! is selected, the application displays an alert, letting the user know that everything is OK (see Figure 4-4).

A329781_4_En_4_Fig4_HTML.jpg
Figure 4-4. Alerts notify the user when important things happen. This app uses one to confirm that everything went OK.

Understanding Active, Static, and Passive Controls

Interface controls operate in three basic modes: active, static (or inactive), and passive. The buttons that you used in the previous chapter provide classic examples of active controls. You push them, and something happens—usually, a piece of Swift code you’ve written executes.

Although many of the controls you use directly trigger action methods, not all controls work this way. The image view that you’ll be implementing in this does nothing other than show an image in your application; however, it can be configured to trigger action methods—here, the user doesn’t do anything with it. Labels and image controls often work in this manner.

Some controls operate in a passive mode, simply holding on to a value that the user has entered until you’re ready for it. These controls don’t trigger action methods, but the user can interact with them and change their values. A classic example of a passive control is a text field on a web page. Although it’s possible to create validation code that fires when the user tabs out of a field, the vast majority of web page text fields simply contain data that’s submitted to the server when the user clicks the submit button. The text fields themselves usually don’t cause any code to fire, but when the submit button is clicked, the text field’s data gets passed to the associated Swift code.

On an iOS device, most of the available controls function in all three modes, and nearly all of them can function in more than one mode, depending on your needs. All iOS controls exist as subclasses of UIControl, which makes them capable of triggering action methods. Many controls can be used passively, and all of them can be made inactive or invisible. For example, using one control might trigger another inactive control to become active. However, some controls, such as buttons, really don’t serve much purpose unless they are used in an active manner to trigger code.

Some behavioral differences exist between controls on iOS and those on your Mac. Here are a few examples:

  • Because of the multitouch interface, all iOS controls can trigger multiple actions, depending on how they are touched. The user might trigger a different action with a finger swipe across the control than with just a tap.

  • You could have one action fire when the user presses down on a button and a separate action fire when the finger is lifted off the button.

  • You could have a single control call multiple action methods on a single event. For example, you could have two different action methods fire on the Touch Up Inside event when the user’s finger is lifted after touching that button.

Note

Although controls can trigger multiple methods on iOS, the vast majority of the time you’re better off implementing a single action method that does what you need for a particular use of a control. You won’t usually need this capability, but it’s good to keep it in mind when working in Interface Builder. Connecting an event to an action in Interface Builder does not disconnect a previously connected action from the same control! This can lead to surprising misbehaviors in your app, where a control will trigger multiple action methods. Keep an eye open when retargeting an event in Interface Builder, and make sure you remove old actions before connecting to new ones.

Another major difference between iOS and the Mac stems from the fact that, normally, iOS devices do not have a physical keyboard. The standard iOS software keyboard is actually just a view filled with a series of button controls that are managed for you by the system. Your code will likely never directly interact with the iOS keyboard.

Creating the ControlFun Application

Open Xcode and create a new project called ControlFun. You’ll use the Single View App template again, so create the project just as you did in the previous two chapters.

Now that you’ve created your project, let’s get the image you’ll use in your image view. The image must be imported into Xcode before it will be available for use inside Interface Builder, so let’s do that now. You’ll find three files in the 04 - Logos folder in the example source code archive, named apress_logo.png, [email protected], and [email protected], which are a standard version and two Retina versions of the same image. You’re going to add all three of these to the new project’s asset catalog and let the app decide which of them to use at runtime. If you’d rather use a set of images of your own choosing, make sure they are .png images sized correctly for the space available. The small version should be less than 100 pixels tall and a maximum of 300 pixels wide so that it can fit comfortably at the top of the view on the narrowest iPhone screen without being resized. The larger ones should be twice and three times the size of the small version, respectively.

In Xcode, select Assets.xcassets in the Project Navigator ; then go to the Logos folder in the Finder and select all three images. Now drag the images onto the editor area in Xcode and release the mouse. Xcode uses the image names to figure out that you are adding three versions of an image called apress_logo and does the rest of the work for you (see Figure 4-5). You’ll see that there is now an apress_logo entry in the left column of the editing area below the AppIcon entry that you started with. You can now use the name apress_logo in code or in Interface Builder to refer to this image set and the correct one will be loaded at runtime.

A329781_4_En_4_Fig5_HTML.jpg
Figure 4-5. Adding the apress_logo images into the Xcode project

Implementing the Image View and Text Fields

With the image added to your project , your next step is to implement the five interface elements at the top of the application’s screen: the image view, the two text fields, and the two labels, as shown in Figure 4-6.

A329781_4_En_4_Fig6_HTML.jpg
Figure 4-6. The image view, labels, and text fields you implement first

Adding the Image View

In the Project Navigator , click Main.storyboard to open the main storyboard in Interface Builder. You’ll see the familiar white background and a single square view where you can lay out your application’s interface. As you did in the previous chapter, beneath the IB window select iPhone 7 as your “View as” choice.

Note

This section below the canvas, which came out with Xcode 8 and is known as the view dimension, allows you to select how you view the scene you’re working with in your IB canvas.

If the Object Library is not open, select View ➤ Utilities ➤ Show Object Library to open it. Scroll about one-fourth of the way through the list until you find ImageView (see Figure 4-7) or just type image in the search field. Remember that the Object Library corresponds to the third icon on top of the Library pane.

A329781_4_En_4_Fig7_HTML.jpg
Figure 4-7. The Image View element in Interface Builder’s Object Library

Drag an image view onto the view in the storyboard editor and drop it somewhere near the top of the view, as shown in Figure 4-8. Don’t worry about exactly positioning yet—you’ll adjust that in the next section.

A329781_4_En_4_Fig8_HTML.jpg
Figure 4-8. Adding a UIImageView to your storyboard

With the image view selected, bring up the Attributes Inspector by pressing ⌥⌘ 4. You should see the editable options of the UIImageView class. The most important setting for your image view is the topmost item in the inspector, labeled Image. Click the little arrow to the right of the field to see a pop-up menu that lists the available images. This list includes any images you added to your project’s assets catalog. Select the apress_logo image you added earlier, and it should appear in your image view , as shown in Figure 4-9.

A329781_4_En_4_Fig9_HTML.jpg
Figure 4-9. The image view Attributes Inspector. You selected your image from the Image pop-up at the top of the inspector, and this populated the image view with your image.

Resizing the Image View

The image you used is not the same size as the image view in which it was placed. Xcode, by default, scales the image to completely fill its image view. A big clue that this is so is the Mode setting in the Attributes Inspector, which is set to Scale To Fill. Though you could keep your app this way, it’s generally a good idea to do any image scaling that’s needed before runtime, as image scaling takes time and processor cycles. In this case, you don’t want any scaling at all, so let’s resize your image view to the exact size of your image. Start by changing the Mode attribute to Center, which says that the image should not be scaled and should be centered in whatever space is assigned to the image view. Now let’s fit the image view to the size of the image. To do that, make sure the image view is selected so that you can see its outline and resize handles and then press ⌘= or select Editor ➤ Size to Fit Content in Xcode’s menu. If pressing ⌘= does not work or Size to Fit Content is grayed out, reselect the image view, drag it a little way to the side, and try again.

Tip

If you encounter difficulty selecting an item in the editing area, you can open the Document Outline by clicking the small rectangular icon in the lower-left corner. Now, click the item you want selected in the Document Outline and, sure enough, that item will be selected in the editor.

To get at an object that is nested inside another object, click the disclosure triangle to the left of the enclosing object to reveal the nested object. In this case, to select the image view, first click the disclosure triangle to the left of the view. Then, when the image view appears in the Document Outline, click it, and it will be selected in the editing area.

Now that the image view is resized, let’s move it back to its centered position . You already know how to do that because you did the same thing in Chapter 3. Drag the image view until it’s horizontally centered, click the Align icon at the bottom right of the editor area, check the Horizontally in Container check box, and click Add 1 Constraint.

You may notice that Interface Builder shows some solid lines running from an edge of one view to an edge of its superview (not to be confused with the dashed blue lines that are shown while you’re dragging things around) or from one side of the superview to another. These solid lines represent the constraints that you have added. If you select the constraint that you just added by clicking it, you’ll see that it becomes a solid orange line that runs the entire height of the main view, as shown in Figure 4-10.

A329781_4_En_4_Fig10_HTML.jpg
Figure 4-10. Once you have resized your image view to fit the size of its image, you drag it into position using the view’s blue guidelines and create a constraint to keep it centered

The solid line indicates that you have selected the constraint. The fact that it is orange means that the position or size of the image view is not yet fully specified and so you need to add more constraints. You can find out what the problem is by clicking the orange triangle in the activity view. In this case, Xcode is telling you that you need to set a vertical constraint for the image view. You can either do so now, using the techniques you saw in Chapter 3, or wait until you fix all the constraints for your layout later in the chapter.

Tip

Dragging and resizing views in Interface Builder can be tricky. Don’t forget about the Document Outline, which you can open by clicking the small rectangular icon at the bottom left of the editing area. When it comes to resizing, hold down the ➤ key, and Interface Builder will draw some helpful red lines on the screen that make it much easier to get a sense of the image view’s position. This trick won’t work with dragging since the ➤ key will prompt Interface Builder to make a copy of the dragged object. However, if you select Editor ➤ Canvas ➤ Show Bounds Rectangles, Interface Builder will draw a line around all of your interface items, making them easier to see. You can turn off those lines by selecting Show Bounds Rectangles a second time.

Setting View Attributes

Select your image view and then switch your attention back over to the Attributes Inspector. Below the Image View section in the inspector is the View section. As you may have deduced, the pattern here is that the attributes that are specific to the selected object are shown at the top, followed by more general attributes that apply to the selected object’s parent class. In this case, the parent class of UIImageView is UIView, so the next section is simply labeled View, and it contains attributes that any view class has.

Using the Mode Attribute

The first option in the view inspector is a pop-up menu labeled Mode. The Mode menu defines how the view will display its content. As you’ve already seen, in the case of the image view, this determines how the image will be aligned inside the view and whether it will be scaled to fit. Feel free to try various options for apress_logo, but remember to reset it to Center when you have finished.

As noted earlier, choosing any option that causes the image to scale will potentially add processing overhead at runtime, so it’s best to avoid that whenever possible and size your images correctly before you import them. If you want to display the same image at multiple sizes, generally it’s better to have multiple copies of the image at different sizes in your project, rather than force the iOS device to do scaling at runtime. Of course, there are times when scaling at runtime is appropriate and even unavoidable; this is a guideline, not a rule.

Using the Semantic Attribute

Immediately below Mode, you’ll find the Semantic attribute . Added in iOS 9, this attribute lets you specify how the view should be rendered in a locale that uses a right-to-left reading order, such as Hebrew or Arabic. By default, the view is unspecified, but you can change this by selecting an appropriate value here. Refer to the description of the semanticContentAttribute property in the Xcode documentation for the UIView class for more details.

Using Tag

The next item, Tag , is worth mentioning, though you won’t be using it in this chapter. All subclasses of UIView, including all views and controls, have a property called tag, which is just a numeric value that you can set here or in code. The tag is designed for your use—the system will never set or change its value. If you assign a tag value to a control or view, you can be sure that the tag will always have that value unless you change it.

Tags provide an easy, language-independent way of identifying objects in your interface. Let’s say that you have five different buttons, each with a different label, and you want to use a single action method to handle all five buttons. In that case, you probably need some way to differentiate among the buttons when your action method is called. Unlike labels, tags will never change, so if you set a tag value here in Interface Builder, you can use it as a fast and reliable way to check which control was passed into an action method by using the sender argument.

Using Interaction Check Boxes

The two check boxes in the Interaction section have to do with user interaction. The first check box, User Interaction Enabled, specifies whether the user can do anything at all with this object. For most controls, this box will be selected because, if it’s not, the control will never be able to trigger action methods. However, image views default to unchecked because they are often used just for the display of static information. Since all you’re doing here is displaying a picture on the screen, there is no need to turn this on.

The second check box is Multiple Touch, and it determines whether this control is capable of receiving multitouch events. Multitouch events allow complex gestures such as the pinch gesture used to zoom in on many iOS applications. Since this image view doesn’t accept user interaction at all, there’s no reason to turn on multitouch events, so leave this check box deselected.

Using the Alpha Value

The next item in the inspector is Alpha. Be careful when using Alpha as it defines how transparent your view is—how much of what’s beneath it shows through. It’s defined as a floating-point number between 0.0 and 1.0, where 0.0 is fully transparent and 1.0 is completely opaque. If you use any value less than 1.0, your iOS device will draw this view with some amount of transparency so that any objects behind it show through. With a value of less than 1.0, even if there’s nothing interesting behind your view, you will cause your application to spend processor cycles compositing your partially transparent view over the emptiness behind it. Therefore, don’t set Alpha to anything other than 1.0 unless you have a very good reason for doing so.

Using Background

The next item down, Background , determines the color of the background for the view. For image views, this matters only when an image doesn’t fill its view and is letterboxed or when parts of the image are transparent. Since you’ve sized your view to perfectly match your image, this setting will have no visible effect, so you can leave it alone.

Using Tint

The next control lets you specify a tint color for the selected view. This is a color that some views use when drawing themselves. The segmented control that you’ll use later in this chapter colors itself using its tint color, but the UIImageView does not.

Drawing Check Boxes

Below Tint you’ll find a series of Drawing check boxes . The first one is labeled Opaque. That should be checked by default; if not, click to select that check box. This tells iOS that nothing behind your view needs to be drawn and allows iOS’s drawing methods to do some optimizations that speed up drawing.

You might be wondering why you need to select the Opaque check box when you’ve already set the value of Alpha to 1.0 to indicate no transparency. The Alpha value applies to the parts of the image to be drawn; if an image doesn’t completely fill the image view or there are holes in the image thanks to an alpha channel, the objects below will still show through, regardless of the value set in Alpha. By selecting Opaque, you are telling iOS that nothing behind this view ever needs to be drawn, no matter what, so it does not need to waste processing time with anything behind your object. You can safely select the Opaque check box because you selected Size To Fit earlier, which caused the image view to match the size of the image it contains.

The Hidden check box does exactly what you think it does. If it’s selected, the user can’t see this object. Hiding an object can be useful at times, as you’ll see later in this chapter when you hide your switches and button; however, the vast majority of the time—including now—you want this to remain unchecked.

The next check box, Clears Graphics Context, will rarely need to be selected. When it is selected, iOS will draw the entire area covered by the object in transparent black before it actually draws the object. Again, it should be turned off for the sake of performance and because it’s rarely needed. Make sure this check box is deselected (it is likely selected by default).

Clip Subviews is an interesting option. If your view contains subviews and those subviews are not completely contained within the bounds of its parent view, this check box determines how the subviews will be drawn. If Clip Subviews is selected, only the portions of subviews that lie within the bounds of the parent will be drawn. If Clip Subviews is deselected, subviews will be drawn completely, even if they lie outside the bounds of the parent.

Clip Subviews is deselected by default. It might seem that the default behavior should be the opposite of what it actually is so that child views won’t be able to draw all over the place. However, calculating the clipping area and displaying only part of the subviews is a somewhat costly operation, mathematically speaking; most of the time, a subview won’t lie outside the bounds of its superview. You can turn on Clip Subviews if you really need it for some reason, but it is off by default for the sake of performance.

The last check box in this section, Autoresize Subviews, tells iOS to resize any subviews if this view is resized. Leave this selected, but since you don’t allow your view to be resized, it really does not matter whether it’s selected or not.

Stretching

Next you’ll see a section simply labeled Stretching , which refers to the form of rectangular views being redrawn as they're resized on the screen. The idea is that, rather than the entire content of a view being stretched uniformly, you can keep the outer edges of a view, such as the beveled edge of a button, looking the same even as the center portion stretches.

The four floating-point values set here let you declare which portion of the rectangle is stretchable by specifying a point at the upper-left corner of the view and the size of the stretchable area, all in the form of a number between 0.0 and 1.0 that represents a portion of the overall view size. For example, if you wanted to keep 10 percent of each edge not stretchable, you would specify 0.1 for both X and Y and specify 0.8 for both Width and Height. In this case, you're going to leave the default values of 0.0 for X and Y and 1.0 for Width and Height. Most of the time, you will not change these values .

Adding the Text Fields

With your image view finished, it’s time to bring on the text fields. Grab a text field from the Object Library and drag it onto the storyboard. Use the blue guidelines to align it with the right margin and place it a little way below the image view, as shown in Figure 4-11.

A329781_4_En_4_Fig11_HTML.jpg
Figure 4-11. Drag a text field out of the library and drop it onto the view, just below the image view and touching the right side’s blue guideline

Next, grab a label from the library and then drag that over so it is aligned with the left margin of the view and vertically with the text field you placed earlier. Notice that multiple blue guidelines will pop up as you move the label around, making it easy to align the label to the text field using the top, bottom, or middle of the label. You’re going to align the label and the text field using the baseline , which shows up as you’re dragging around the middle of those guidelines (see Figure 4-12).

A329781_4_En_4_Fig12_HTML.jpg
Figure 4-12. Aligning the label and text field using the baseline guide

Double-click the label you just dropped, change it to read Name: instead of Label (note the colon character at the end of the label), and press the Enter key to commit your changes.

Next, drag another text field from the library to the view and use the guidelines to place it below the first text field, as shown in Figure 4-13.

A329781_4_En_4_Fig13_HTML.jpg
Figure 4-13. Adding the second text field

Once you’ve added the second text field, grab another label from the library and place it on the left side, below the existing label. Again, use the middle blue guideline to align your new label with the second text field. Double-click the new label and change it to read Number: (again, don’t forget the colon).

Now, let’s expand the size of the bottom text field to the left, so it is up against the right side of the label. Why start with the bottom text field? You want the two text fields to be the same size, and the bottom label is longer.

Single-click the bottom text field and drag the left resize dot to the left until a blue guideline appears to tell you that you are as close as you should ever be to the label, as shown in Figure 4-14. This particular guideline is somewhat subtle—it’s only as tall as the text field itself, so be sure to look closely.

A329781_4_En_4_Fig14_HTML.jpg
Figure 4-14. Expand the width of the bottom text field

Now, expand the top text field in the same way so that it matches the bottom one in size. Once again, a blue guideline provides some help, and this one extends all the way down to the other text field, making it easier to spot.

You’re basically finished with the text fields , except for one small detail. Look back at Figure 4-1. Do you see how the Name: and Number: are right-aligned? Right now, yours are both against the left margin. To align the right sides of the two labels, click the Name: label, hold down the ⇧ (Shift) key, and click the Number: label so both labels are selected. Next, press ⌥⌘4 to bring up the Attributes Inspector and make sure the Label section is expanded so you can see the label-specific attributes. If it’s not expanded, click the Show button on the right of the header to open it. Now use the Alignment control in the inspector to make the content of these labels right-justified and then make a constraint to make sure that these two fields are always the same width by clicking the Pin icon at the bottom of the editing area, checking the Equal Widths check box in the pop-up that appears, and clicking Add 1 Constraint. At this point, you’ll have an orange warning triangle in the activity view and some layout warnings in the Issue Navigator. Ignore these for now—you’ll fix them later.

When you are finished, this part of the interface should look very much like Figure 4-1. The only difference is the light-gray text in each text field. You’ll add that now. Select the top text field (the one next to the Name: label) and press ⌥⌘4 to bring up the Attributes Inspector (see Figure 4-15). The text field is one of the most complex iOS controls, as well as one of the most commonly used. Let’s look through the settings, beginning from the top of the inspector. Make sure you’ve selected the text field and not the label or other elements.

A329781_4_En_4_Fig15_HTML.jpg
Figure 4-15. The inspector for a text field showing the default values

Using Text Field Inspector Settings

In the first section, the Text label is linked to two input fields that give you some control over the text that will appear in the text field. The upper one is a pop-up button that lets you choose between plain text and attributed text, which can contain a variety of fonts and other attributes. You used attributed text to add bold to part of the text in your example in Chapter 3. Let’s leave that pop-up button set to Plain for now. Immediately below that, you can set a default value for the text field. Whatever you type here will show up in the text field when your application launches, instead of just a blank space.

After that comes a couple of controls that let you set the font and font color. You’ll leave Color at the default value of black. Note that the Color pop-up is divided into two parts. The right side allows you to select from a set of preselected colors, and the left side gives you access to a color picker to more precisely specify your color.

The Font setting is divided into three parts. On the right side is a control that lets you increment or decrement the text size, one point at a time. The left side allows you to manually edit the font name or size. You can click the T-in-a-box icon to bring up a pop-up window that lets you set the various font attributes. You’ll leave the font at its default setting of System 14.0 or to whatever size your configuration may be set.

Below these fields are five buttons for controlling the alignment of the text displayed in the field. You’ll leave this setting at the default value of left-aligned (the leftmost button).

Rounding out this first section, Placeholder allows you to specify a bit of text that will be displayed in gray inside the text field, but only when the field does not have a value. You can use a placeholder instead of adding a label to the layout (as you did) if space is tight, or you can use it to clarify what the user should type in this text field. Type the text Type in a name as the placeholder for your currently selected text field and then hit Enter to commit the change.

The next two fields, Background and Disabled, are used only if you need to customize the appearance of your text field, which is unnecessary and actually ill-advised the vast majority of the time. Users expect text fields to look a certain way. You’ll leave these set to their defaults .

Next are four buttons labeled Border Style. These allow you to change the way the text field’s edge will be drawn. The default value (the rightmost button) creates the text field style that users are most accustomed to seeing for normal text fields in an iOS application. You may want to look at all four different styles, but when you’re finished, put this setting back to the rightmost button.

Below the border setting is a Clear button pop-up button, which lets you choose when the Clear button should appear. The Clear button is the small X that can appear at the right end of a text field. Clear buttons are typically used with search fields and other fields where you would be likely to change the value frequently. They are not typically included on text fields used to persist data, so leave this at the default value of “Never appears.”

The Clear When Editing Begins check box specifies what happens when the user touches this field. If this box is checked, any value that was previously in this field will be deleted, and the user will start with an empty field. If this box is unchecked, the previous value will remain in the field, and the user will be able to edit it. Leave this deselected.

The next section starts with a control that lets you set the minimum font size that the text field will use for displaying its text. Leave that at its default value for now. The Adjust to Fit check box specifies whether the size of the text should shrink if the text field is reduced in size. Adjusting to fit will keep the entire text visible in the view, even if the text would normally be too big to fit in the allotted space. This check box works in conjunction with the minimum font size setting. No matter the size of the field , the text will not be resized below that minimum size. Specifying a minimum size allows you to make sure that the text doesn’t get too small to be readable.

The next section defines how the keyboard will look and behave when this text field is being used. Since you’re expecting a name, let’s change the Capitalization pop-up to Words. This causes the first letter of every word to be automatically capitalized, which is what you typically want with names.

The next four pop-ups—Correction, Spell Checking, Keyboard Type, and Appearance—can be left at their default values. Take a minute to look at each to get a sense of what these settings do.

Next is the Return Key pop-up. The Return key is on the lower right of the virtual keyboard, and its label changes based on what you’re doing. If you are entering text in Safari’s search field, for example, it says Search. In an application like this one, where the text fields share the screen with other controls, Done is the right choice. Make that change here.

If the Auto-enable Return Key check box is selected, the Return key is disabled until at least one character is typed into the text field. Leave this deselected because you want to allow the text field to remain empty if the user prefers not to enter anything.

The Secure check box specifies whether the characters being typed are displayed in the text field. You would check this check box if the text field was being used as a password field. Leave it unchecked for your app.

The next section (which you will probably have to scroll down to see) allows you to set control attributes inherited from UIControl; however, these generally don’t apply to text fields and, with the exception of the Enabled check box, won’t affect the field’s appearance. You want to leave these text fields enabled so that the user can interact with them. Leave the default settings in this section.

The last section on the inspector, View, should look familiar. It’s identical to the section of the same name in the Image View Inspector you looked at earlier. These are attributes inherited from the UIView class; since all controls are subclasses of UIView, they all share this section of attributes. As you did earlier for the image view, select the Opaque check box and deselect Clears Graphics Context and Clip Subviews—for the reasons discussed earlier.

Setting the Attributes for the Second Text Field

Next, single-click the lower text field (the one next to the Number: label) in the storyboard and return to the Attributes Inspector. In the Placeholder field, type Type in a number and make sure Clear When Editing Begins is deselected. A little farther down, click the Keyboard Type pop-up menu. Since you want the user to enter only numbers, not letters, select Number Pad. On the iPhone, this ensures that the users will be presented with a keyboard containing only numbers, meaning they won’t be able to enter alphabetical characters, symbols, or anything other than numbers. You don’t need to set the Return Key value for the numeric keypad because that style of keyboard doesn’t have a Return key; therefore, all of the other inspector settings can stay at the default values. As you did earlier, select the Opaque check box and deselect Clears Graphics Context and Clip Subviews. On the iPad, selecting Number Pad has the effect of bringing up a full virtual keyboard in numeric mode when the user activates the text field, but the user can switch back to alphabetic input. This means that in a real application, you would have to verify that the user actually entered a valid number when processing the content of the Number field.

Tip

If you really want to stop the user from typing anything other than numbers into a text field, you can do so by creating a class that implements the textView(_ textView: shouldChangeTextInRange: replacementText text:) method of the UITextViewDelegate protocol and making it the text view’s delegate. The details are not too complex but are beyond the scope of this book.

Adding Constraints

Before you continue, you need to adjust some layout constraints . When you drag a view into another view in Interface Builder (as you just did), Xcode doesn’t create any constraints for it automatically. The layout system requires a complete set of constraints, so when it’s time to compile your code, Xcode generates a set of default constraints describing the layout. The constraints depend on each object’s position within its superview; when it’s nearer the left or right edge, it will be pinned to the left or the right, respectively. Similarly, depending on whether it’s nearer the top or the bottom edge, it will be pinned to the top or the bottom. If it’s centered in either direction, it typically gets a constraint pinning it to the center.

To complicate matters further, Xcode may also apply automatic constraints pinning each new object to one or more of its “sibling” objects within the same superview. This automatic behavior may or may not be what you want, so normally you’re better off creating a complete set of constraints within Interface Builder before your app is compiled, and in the previous two chapters, you worked through some examples.

Let’s again look at where you’re at on this. To see all the constraints that are in play for any particular view, try selecting it and opening the Size Inspector. If you select either of the text fields, you’ll see that the Size Inspector shows a message claiming that there are no constraints for the selected view. In fact, this GUI you’ve been building has only the constraints that you applied earlier, binding the horizontal centers of the image view and the container view and making the labels equally sized. Click the image view and the labels to see these constraints in the inspector.

What you really want is a full set of constraints to tell the layout system precisely how to handle all of your views and controls, just as it would get at compile time. Fortunately, this is pretty simple to accomplish. Select all the views and controls by click-dragging a box around them, from inside the upper-left corner of your container view down toward the lower right. If you start dragging and find that the view starts moving instead, just release the mouse, move it a little bit further inside the view, and try again. When all items are selected, use the menu to execute the Editor ➤ Resolve Auto Layout Issues ➤ Add Missing Constraints command from the All Views in View Controller section of the menu. After doing that, you’ll see that all your views and controls now have some little blue sticks connecting them to one another and to the container view. Each of those sticks represents a constraint. The advantage of creating these now instead of letting Xcode create them at compile time is that you have a chance to modify each constraint if you need to do so. You’ll explore more of what you can do with constraints throughout the book.

Tip

Another way to apply constraints to all the views owned by a view controller is to select the view controller in the Document Outline and then choose Editor ➤ Resolve Auto Layout Issues ➤ Add Missing Constraints.

At this point, with all the necessary constraints in place, you can fix the layout warnings in the Issue Navigator. To do that, select the view controller in the Document Outline and then click Editor ➤ Resolve Auto Layout Issues ➤ Update Frames in the Xcode menu. The layout warnings should be gone.

Creating and Connecting Outlets

For this first part of the interface , all that’s left is creating and connecting your outlets. The image view and labels on your interface do not need outlets because you don’t need to change them at runtime. The two text fields, however, will hold data you’ll need to use in your code, so you need outlets pointing to each of them.

As you probably remember from the previous chapter, Xcode allows you to create and connect outlets at the same time using the Assistant Editor, which should already be open (but if it’s not, open it as described earlier).

Make sure your storyboard file is selected in the Project Navigator . If you don’t have a large amount of screen real estate, you might also want to select View ➤ Utilities ➤ Hide Utilities to hide the Utilities area during this step. In the Assistant Editor’s jump bar, select Automatic. You should see the ViewController.swift file, as shown in Figure 4-16.

A329781_4_En_4_Fig16_HTML.jpg
Figure 4-16. The storyboard editing area with the Assistant Editor open. You can see the Assistant Editor on the right, showing the code from ViewController.swift.

Let’s start connecting things up. Control-drag from the top text field to the ViewController.swift file, right below the ViewController line. You should see a gray pop-up that reads Insert Outlet, Action, or Outlet Collection (see Figure 4-17). Release the mouse button and you’ll get the same pop-up you saw in the previous chapter. You want to create an outlet called nameField, so type nameField into the Name field and then hit Return or click the Connect button.

A329781_4_En_4_Fig17_HTML.jpg
Figure 4-17. With the Assistant Editor open, you Control-drag over to the source code in order to simultaneously create the nameField outlet and connect it to the appropriate text field.

You now have a property called nameField in ViewController, and it has been connected to the top text field. Do the same for the second text field, creating and connecting it to a property called numberField. When you’ve done that, your code should look like Listing 4-1.

Listing 4-1. Your Connected Text Fields
class ViewController: UIViewController {
    @IBOutlet weak var nameField: UITextField!
    @IBOutlet weak var numberField: UITextField!

Closing the Keyboard

Let’s see how the app works so far, select Product ➤ Run. The application should come up in the iOS simulator. Click the Name text field, and the traditional keyboard should appear.

Tip

If the keyboard does not appear, the simulator may be configured to work as if a hardware keyboard had been connected. To fix that, deselect Hardware ➤ Keyboard ➤ Connect Hardware Keyboard in the iOS simulator menu and try again.

Type a name and then tap the Number field. The numeric keypad should appear, as shown in Figure 4-18. Cocoa Touch gives you all this functionality for free just by adding text fields to your interface.

A329781_4_En_4_Fig18_HTML.jpg
Figure 4-18. The keyboard comes up automatically when you touch either the text field or the number field

But there’s a little problem. How do you get the keyboard to go away? Go ahead and try. You’ll see that nothing happens.

Closing the Keyboard When Done Is Tapped

Because the keyboard is software-based rather than a physical keyboard, you need to take a few extra steps to make sure the keyboard goes away when the user is finished with it. When the user taps the Done button on the text keyboard, a Did End On Exit event will be generated. When that happens, you need to tell the text field to give up control so that the keyboard will go away. To do that, you need to add an action method to your controller class .

Select ViewController.swift in the Project Navigator and add the action method in Listing 4-2 at the bottom of the file, just before the closing brace.

Listing 4-2. Method to Close the Keyboard When Done
@IBAction func textFieldDoneEditing(sender: UITextField) {
    sender.resignFirstResponder()
}

As you saw in Chapter 2, the first responder acts as the control with which the user is currently interacting. In your new method, you tell your control to resign as a first responder, giving up that role to the previous control the user worked with. When a text field yields first responder status, the keyboard associated with it goes away.

Save the ViewController.swift file. Let’s look back at the storyboard and arrange to trigger this action from both of your text fields.

Select Main.storyboard in the Project Navigator , single-click the Name text field, and press ⌥⌘6 to bring up the Connections Inspector. This time, you don’t want the Touch Up Inside event that you used in the previous chapter. Instead, you want Did End On Exit since that event will fire when the user taps the Done button on the text keyboard.

Drag from the circle next to Did End On Exit to the yellow View Controller icon in the storyboard, in the bar that’s just above the view you’ve been configuring, and let go. A small pop-up menu will appear containing the name of a single action, the one you just added. Click the textFieldDoneEditing action to select it. You can also do this by dragging from the circle in the Connections Inspector to the textFieldDoneEditing() method in the Assistant Editor, if you still have it open. Repeat this procedure with the other text field, save your changes, and then run the app again.

When the simulator appears, click the Name field, type something, and then tap the Done button. As expected, the keyboard drops away, but what about the Number field, since there is no Done button, as you can see back in Figure 4-18?

Not all keyboard layouts, including the numeric keypad, include a Done button. You could force the user to tap the Name field and then tap Done, but that’s not very user-friendly. And you most definitely want your application to be user-friendly. Let’s see how to handle this situation.

Touching the Background to Close the Keyboard

Apple’s iPhone applications allow tapping in most text fields—anywhere in the view where there’s no active control that causes the keyboard to go away. Let’s implement that for your app.

The answer may surprise you because of its simplicity. The view controller includes a property called view that it inherited from UIViewController. This view property corresponds to the main view in the storyboard. The view property points to an instance of UIView that acts as a container for all the items in your user interface. It is sometimes referred to as a container view because its main purpose is to simply hold other views and controls. Essentially, the container view provides the background to your user interface. All you need to do is detect when the user taps it. There are a couple of ways to do that. First, there are methods in the UIResponder class, from which UIView is derived, that are called whenever the user places one or more fingers onto a view, moves those fingers around, or lifts them up. You can override one of those methods (specifically the one that’s called when the user lifts a finger from the screen) and add your code in there. The other way to do this is to add a gesture recognizer to the container view. Gesture recognizers listen to the events that are generated when the user interacts with a view and try to figure out what the user is doing. There are several different gesture recognizers that respond to different sequences of actions. The one that you need to use is the tap gesture recognizer, which signals an event when the user puts a finger on the screen and then lifts it up again within a reasonably short time.

To use a gesture recognizer, you create an instance, configure it, link it to the view that you want it to monitor for touch events, and attach it to an action method in your view controller class . When the gesture is recognized, your action method is called. You can create and configure the recognizer in code, or you can do it in Interface Builder. Here, you’ll use Interface Builder because it’s easier. Return to the storyboard and make sure that the Object Library is showing and then locate a tap gesture recognizer, drag it over the storyboard, and drop it onto the container view. The recognizer is not visible at runtime, so you can’t see it in the storyboard, but it appears in the Document Outline, as shown in Figure 4-19.

A329781_4_En_4_Fig19_HTML.jpg
Figure 4-19. Tap gesture recognizer in the Document Outline

Selecting the gesture recognizer reveals its configurable attributes in the Attributes Inspector, as shown in Figure 4-20.

A329781_4_En_4_Fig20_HTML.jpg
Figure 4-20. The attributes of the tap gesture recognizer

The Taps field specifies how many times the user needs to tap before the gesture is recognized, and the Touches field controls how many fingers need to be tapped. The defaults of one tap with one finger are exactly what you need, so leave both fields unchanged. The other attributes are fine too, so all you need to do is link the recognizer to an action method. To do that, open ViewController.swift in the Assistant Editor and Control-drag from the recognizer in the Document Outline to the line just above the closing brace in ViewController.swift. Release the mouse when you see the usual gray pop-up like the one shown in Figure 4-16. In the pop-up that opens, change the connection type to Action and the method name to onTapGestureRecognized to have Xcode add the action method and link it to the gesture recognizer. This method will be called whenever the user taps the main view. All you need to do is add the code to close the keyboard, if it’s open. You already know how to do that, so change the code to that shown in Listing 4-3.

Listing 4-3. Your Gesture Recognizer Code to Remove the Keyboard
@IBAction func onTapGestureRecognized(sender: AnyObject) {
    nameField.resignFirstResponder()
    numberField.resignFirstResponder()
}

This code simply tells both text fields to yield first responder status if they have it. It is perfectly safe to call resignFirstResponder() on a control that is not the first responder so you can call it on both text fields without needing to check whether either is the first responder. Build and run your application again, and this time, the keyboard should disappear not only when the Done button is tapped but also when you tap anywhere that’s not an active control, which is the behavior that your users will expect.

Adding the Slider and Label

Let’s add a slider and a label , the idea being that the value shown by the label will change as the slider is moved. Select Main.storyboard in the Project Navigator so you can add more items to your application’s user interface. From the Object Library, bring over a slider and arrange it below the Number text field, using the right side’s blue guideline as a stopping point and leaving a little breathing room between the slider and the bottom text field. Single-click the newly added slider to select it and then press ⌥⌘4 to go back to the Attributes Inspector if it’s not already visible (see Figure 4-21).

A329781_4_En_4_Fig21_HTML.jpg
Figure 4-21. The inspector showing default attributes for a slider

A slider lets you choose a number in a given range. Use the inspector to set the Minimum value to 1, the Maximum value to 100, and the Current value to 50. Leave the Events Continuous Update check box selected. This ensures a continuous flow of events as the slider’s value changes.

Bring over a label and place it next to the slider, using the blue guidelines to align it vertically with the slider and to align its left edge with the left margin of the view (see Figure 4-22).

A329781_4_En_4_Fig22_HTML.jpg
Figure 4-22. Placing the slider’s label

Double-click the newly placed label, and change its text from Label to 100. This is the largest value that the slider can hold, and you can use that to determine the correct width of the slider . Since 100 is shorter in length than Label, Interface Builder automatically makes the label smaller for you, as if you had dragged the right-middle resize dot to the edge. Despite this automatic behavior, you’re still free to resize the label however you want, of course. If you later decide you want the tool to pick the optimum size for you again, just press ⌘= or select Editor ➤ Size to Fit Content.

Next, resize the slider by single-clicking the slider to select it and dragging the left resize handle to the left until the blue guidelines indicate that you’re getting close to the label’s right-side edge.

Now that you’ve added two more controls, you need to add the matching Auto Layout constraints. You’ll do it the easy way again this time, so just select the View Controller icon in the Document Outline and then click Editor ➤ Resolve Auto Layout Issues ➤ Add Missing Constraints. Xcode adjusts the constraints so that they match the positions of all of the controls on-screen.

Creating and Connecting the Actions and Outlets

All that’s left to do with these two controls is to connect them to an outlet and an action—you need an outlet that points to the label so that you can update the label’s value when the slider is used, and you are also going to need an action method for the slider to call as it’s changed. Make sure the Assistant Editor is open and showing ViewController.swift and then Control-drag from the slider to just below the onTapGestureRecognized() method in the Assistant Editor. When the pop-up window appears, change the Connection field to Action, type onSliderChanged in the Name field, set Type to UISlider, and then hit Return to create and connect the action.

Next, Control-drag from the newly added label (the one showing 100) over to the Assistant Editor. This time, drag to just below the numberField property declaration at the top of the file. When the pop-up comes up, type sliderLabel in the Name text field, and then hit Return to create and connect the outlet .

Implementing the Action Method

Though Xcode has created and connected your action method , it’s still up to you to actually write the code that makes up the action method so it does what it’s supposed to do. Change the onSliderChanged() method to that shown in Listing 4-4.

Listing 4-4. Changing the Label Based on the Slider Position/Value
@IBAction func onSliderChanged(_ sender: UISlider) {
    sliderLabel.text = "(lroundf(sender.value))"
}

The call to the lroundf() function (which is part of the standard C library) takes the current value of the slider and rounds it to the nearest integer. The rest of the line converts the value to a string containing that number and assigns it to the label .

That takes care of your controller’s response to the movements of the slider; but to be really consistent, you need to make sure that the label shows the correct slider value before the user even touches it. To do that, add the following line of code to the viewDidLoad() method: sliderLabel.text = "50".

This method executes immediately after the running app loads the view from the storyboard file but before it’s displayed on the screen. The line you added makes sure that the user sees the correct starting value right away.

Save the file. Next, press ⌘R to build and launch your app in the iOS simulator, and try out the slider. As you move it, you should see the label’s text change in real time. Another piece falls into place. But if you drag the slider toward the left (bringing the value below 10) or all the way to the right (setting the value to 100), you’ll see an odd thing happen. The label to the left will shrink horizontally when it drops down to showing a single digit and will grow horizontally when showing three. Now, apart from the text it contains, you don’t actually see the label itself, so you can’t see its size changing, but what you will see is that the slider actually changes its size along with the label, getting smaller or larger. It’s maintaining a size relationship with the label, making sure the gap between the two is always the same.

This is simply a side effect of the way Interface Builder works, helping you create GUIs that are responsive and fluid. You created some default constraints previously, and here you’re seeing one in action. One of the constraints created by Interface Builder keeps the horizontal distance between these elements constant.

Let’s override this behavior by making your own constraint. Back in Xcode, select the label in the storyboard and click the Pin icon at the bottom of the storyboard. In the pop-up, click the Width check box followed by Add 1 Constraint. This makes a new high-priority constraint that tells the layout system, “Don’t mess with the width of this label.” If you now press ⌘R to build and run again, you’ll see that the label no longer expands and contracts as you drag back and forth across the slider .

You’ll see more examples of constraints and their uses throughout the book. But for now, let’s look at implementing the switches .

Implementing the Switches, Button, and Segmented Control

Let’s go back to Xcode once again; this back and forth may seem a bit strange, but it’s fairly common to bounce around between source code, storyboards, and nib files in Xcode, testing your app in the iOS simulator while you’re developing.

Your application will have two switches, which are small controls that can have only two states: on and off. You’ll also add a segmented control to hide and show the switches. Along with that control, you’ll add a button that is revealed when the segmented control’s right side is tapped.

In the storyboard, drag a segmented control from the Object Library (see Figure 4-23) and place it on the View window, a little below the slider and horizontally centered.

A329781_4_En_4_Fig23_HTML.jpg
Figure 4-23. Placing a segmented control onto your storyboard

Double-click the word First on the segmented control and change the title from First to Switches. After doing that, repeat the process with the other segment, renaming it Button (see Figure 4-24), and drag the control back into its centered position.

A329781_4_En_4_Fig24_HTML.jpg
Figure 4-24. Renaming the segments in the segmented control

Adding Two Labeled Switches

Next, take a switch from the library and place it on the view, below the segmented control and against the left margin. Then drag a second switch and place it against the right margin, aligned vertically with the first switch, as shown in Figure 4-25.

A329781_4_En_4_Fig25_HTML.jpg
Figure 4-25. Adding switches to the view
Tip

Holding down the ⌥ key and dragging an object in Interface Builder will create a copy of that item. When you have many instances of the same object to create, it can be faster to drag only one object from the library and then Option-drag as many copies as you need.

The three new controls you’ve added need layout constraints . This time, you’ll add the constraints manually. Start by selecting the segmented control and aligning it to the center of the view by clicking the Align icon, checking Horizontally in Container in the pop-up, and clicking Add 1 Constraint. Next, select the segmented control again and Control-drag upward a little until the background of the main view turns blue. Release the mouse and select Vertical Spacing to Top Layout Guide in the pop-up menu to fix the distance from the segmented control to the top of the view.

Now let’s adjust the switches. Control-drag from the left switch diagonally left and upward relative to the switch and release the mouse. Hold down the Shift key and select Leading Space to Container Margin and Vertical Spacing to Top Layout Guide from the pop-up, release Shift, and press the Return key or click anywhere outside the pop-up to apply the constraints. Do a similar action with the other switch, but this time Control-drag to the upper right relative and select Trailing Space to Container Margin and Vertical Spacing to Top Layout Guide. When you apply constraints by dragging, Xcode offers you different options depending on the direction in which you drag. If you drag horizontally, you’ll have options that let you attach the control to the left or right margins of its parent view, whereas if you drag vertically, Xcode assumes you want to set the position of the control relative to the top or bottom of its parent. Here, you needed one horizontal and one vertical constraint for each switch, so you dragged diagonally to indicate that to Xcode, and you got both horizontal and vertical options.

Connecting and Creating Outlets and Actions

Before you add the button, you’ll create outlets for the two switches and connect them. The button that you’ll be adding next will actually sit on top of the switches, making it harder to Control-drag to and from them, so you want to take care of the switch connections before you add the button. Since the button and the switches will never be visible at the same time, having them in the same physical location won’t be a problem.

Using the Assistant Editor, Control-drag from the switch on the left to just below the last outlet in ViewController.swift. When the pop-up appears, name the outlet leftSwitch and hit Return. Repeat this process with the other switch, naming its outlet rightSwitch.

Now, select the left switch again by single-clicking it. Control-drag once more to the Assistant Editor. This time, drag to just above the brace at the end of the class declaration before letting go. When the pop-up appears, change the Connection field to Action, name the new action method onSwitchChanged(), and set the type of its sender argument to UISwitch. Next, hit Return to create the new action. Now repeat this process with the right switch, with one change: instead of creating a new action, drag the mouse over the onSwitchChanged() method that was just created and connect to it instead. Just as you did in the previous chapter, you’re going to use a single method to handle both switches.

Finally, Control-drag from the segmented control to the Assistant Editor, right below the onSwitchChanged() method. Insert a new action method called toggleControls(), just as you’ve done before. This time, set the type of its sender parameter to UISegmentedControl.

Implementing the Switch Actions

Save the storyboard and let’s add some more code to ViewController.swift, which is already open in the Assistant Editor. Look for the onSwitchChanged() method, changing it to that, as shown in Listing 4-5.

Listing 4-5. Your New onSwitchChanged() Method
@IBAction func onSwitchChanged(_ sender: UISwitch) {
    let setting = sender.isOn
    leftSwitch.setOn(setting, animated: true)
    rightSwitch.setOn(setting, animated: true)
}

The onSwitchChanged() method gets called whenever one of the two switches is tapped. In this method, you simply grab the value of the isOn property of sender (which represents the switch that was pressed) and use that value to set both switches. The idea here is that setting the value of one switch will change the other switch at the other time, keeping them in sync at all times.

Now, sender is always going to be either leftSwitch or rightSwitch, so you might be wondering why you’re setting them both. You do it that way out of practicality since it’s less work to set the value of both switches every time than to determine which the switch made the call and set only the other one. Whichever switch called this method will already be set to the correct value, and setting it again to that same value won’t have any effect.

Adding the Button

Next, go back to Interface Builder and drag a button from the library to your view. Add this button directly on top of the leftmost switch, aligning it with the left margin and vertically aligning its top edge with the top edge of the two switches, as shown in Figure 4-26.

A329781_4_En_4_Fig26_HTML.jpg
Figure 4-26. Adding a button on top of the existing switches

Now, grab the button’s right-center resize handle and drag all the way to the right until you reach the blue guideline that indicates the right-side margin. The button should completely overlay the space occupied by the two switches, but because the default button is transparent, you will still see the switches (see Figure 4-27).

A329781_4_En_4_Fig27_HTML.jpg
Figure 4-27. The button, once placed and resized, fills the space occupied by the two switches

Double-click the newly added button to give it the title Do Something .

The button needs Auto Layout constraints. You’re going to pin it to the top and to both sides of the main view. Control-drag upward from the button until the view background turns blue and then release the mouse and select Vertical Spacing to Top Layout Guide . Then Control-drag horizontally to the left until the main view background turns blue again and select Leading Space to Container Margin. You’ll only get this option if you drag far enough to the left, so if you don’t see it, try again by dragging left until the mouse is outside the bounds of the button. Finally, Control-drag to the right until the main view background turns blue and then select Trailing Space to Container Margin. Now run the application to see what you’ve just done.

Adding an Image to the Button

If you compare your running application to Figure 4-2, you’ll immediately notice a difference. Your Do Something button doesn’t look like the one in the figure. That’s because, starting with iOS 7, the default button displays a very simple appearance: it’s just a piece of plain text with no outline, border, background color, or other decorative features. That conforms nicely to Apple’s design guidelines for iOS 7 and later, but there are still cases where you’ll want to use custom buttons, so I’m going to show you how to do that.

Many of the buttons you see on your iOS device are drawn using images. I’ve provided images that you can use for this example in the Button Images folder of the source code archive for this book. In the Project Navigator in Xcode, select Assets.xcassets (the same assets catalog that you used earlier when you added images for the Apress logo) and then just drag both images from the Button Images folder in the Finder straight into the editing area in your Xcode window. The images are added to your project and will be immediately available to your app.

Using Stretchable Images

If you look at the two button images you just added, you’ll see they’re very small and seem much too narrow to fill out the button you added to the storyboard . That’s because these graphics are meant to be stretchable. It so happens that UIKit can stretch graphics to nicely fill just about any size you want. Stretchable images are an interesting concept. A stretchable image is a resizable image that knows how to resize itself intelligently so that it maintains the correct appearance. For these button templates, you don’t want the edges to stretch evenly with the rest of the image. Edge insets are the parts of an image (measured in pixels) that should not be resized. You want the bevel around the edges to stay the same, no matter what size you make the button, so you need to specify how much nonstretchable space makes up each edge.

In the past, this could be accomplished only in code. You’d have to use a graphics program to measure pixel boundaries of your images and then use those numbers to set edge insets in your code. Xcode 6 eliminated the need for this by letting you visually “slice” any image you have in an assets catalog! That’s what you’re going to do next.

Select the Assets.xcassets asset catalog in Xcode, and inside that select whiteButton. At the bottom right of the editing area, you’ll see a button labeled Show Slicing. Click that to initiate the slicing process, which begins by simply putting a Start Slicing button right on top of your image. Click it. You’ll see three new buttons that let you choose whether you want the image to be sliced (and therefore stretchable) vertically, horizontally, or both. Choose the button in the middle to slice both ways. Xcode does a quick analysis of your image and then finds the sections that seem to have unique pixels around the edges and vertical and horizontal slices in the middle that should be repeatable. You’ll see these boundaries represented by dashed lines, as shown in Figure 4-28. If you have a tricky image, you may need to adjust these (it’s easy to do; just drag them with the mouse). However, for this image, the automatic edge insets will work fine.

A329781_4_En_4_Fig28_HTML.jpg
Figure 4-28. Default slicing for the white button

Next, select blueButton and do the same automatic slicing for it. Now it’s time to put these graphics to use.

Go back to the storyboard you’ve been working on and single-click the Do Something button. With the button selected, press ⌥⌘4 to open the Attributes Inspector. In the inspector, use the Type pop-up menu to change the type from System to Custom. At the bottom of the Button section in the Inspector , you’ll see that you can specify an image and a background for your button. You’re going to use the background to show your resizable graphic, so click in the Background pop-up and select whiteButton. You’ll see that the button now shows the white graphic, perfectly stretched to cover the entire button frame.

You want to use the blue button to define the look of this button’s highlighted state, which is what you see while the button is pressed. I’ll talk more about control states in the next section of this chapter; but for now, just take a look at the second pop-up from the top, labeled State Config. A UIButton can have multiple states, each with its own text and images . Right now you’ve been configuring the default state, so switch this pop-up to Highlighted so that you can configure that state. You’ll see that the Background pop-up has been cleared; click it to select blueButton—and you’re done.

Using Control States

Every iOS control has five possible control states and is always in one, and only one, of these states at any given moment.

  • Default: The most common state is the default control state, which is the default state. It’s the state that controls are in when not in any of the other states.

  • Focused: In focus-based navigation systems, a control enters this state when it receives the focus. A focused control changes its appearance to indicate that it has focus, and this appearance differs from the appearance of the control when it is highlighted or selected. Further interactions with the control can result in it also becoming highlighted or selected.

  • Highlighted: The highlighted state is the state a control is in when it’s currently being used. For a button, this would be while the user has a finger on the button.

  • Selected: Only some controls support the selected state. It is usually used to indicate that the control is turned on or selected. Selected is similar to highlighted, but a control can continue to be selected when the user is no longer directly using that control.

  • Disabled: Controls are in the disabled state when they have been turned off, which can be done by deselecting the Enabled check box in Interface Builder or setting the control’s isEnabled property to NO.

Certain iOS controls have attributes that can take on different values depending on their state. For example, by specifying one image for isDefault and a different image for isHighlighted, you are telling iOS to use one image when the user has a finger on the button and a different image the rest of the time. That’s essentially what you did when you configured two different background states for the button in the storyboard.

Note

In previous versions of this book there were four states: Normal, Highlighted, Disabled, and Selected, with the enumerated values in Objective-C as UIControlStateNormal, UIControlStateHighlighted, UIControlStateEnabled, and UIControlStateSelected. You may see older, pre–Xcode 8 and Swift 3 reference these values.

Connecting and Creating the Button Outlets and Actions

Control-drag from the new button to the Assistant Editor , just below the last outlet already in the section at the top of the file. When the pop-up appears, create a new outlet called doSomethingButton. After you’ve done that, Control-drag from the button a second time to just above the closing brace at the bottom of the file. There, create an action method called onButtonPressed() and set Type to UIButton.

If you save your work and try running the application, you’ll see that the segmented control will be live, but it won’t do anything particularly useful since you still need to add some logic to make the button and switches hide and unhide.

You also need to mark your button as hidden from the start. You didn’t want to do that before because it would have made it harder to connect the outlets and actions. Now that you’ve done that, however, let’s hide the button. You’ll show the button when the user taps the right side of the segmented control; but when the application starts, you want the button hidden. In the storyboard, select the button and press ⌥⌘4 to bring up the Attributes Inspector. Scroll down to the View section and click the Hidden check box. The button will still be visible in Interface Builder.

Implementing the Segmented Control Action

Save the storyboard and focus once again on ViewController.swift. Look for the toggleControls() method that Xcode created for you and add the new code shown in Listing 4-6.

Listing 4-6. Hide or Unhide the Switches Depending on the Segmented Control
@IBAction func toggleControls(_ sender: UISegmentedControl) {
    if sender.selectedSegmentIndex == 0 {  // "Switches" is selected
        leftSwitch.isHidden = false
        rightSwitch.isHidden = false
        doSomethingButton.isHidden = true
    } else {
        leftSwitch.isHidden = true
        rightSwitch.isHidden = true
        doSomethingButton.isHidden = false
    }
}

This code looks at the selectedSegmentIndex property of sender, which tells you which of the sections is currently selected. The first section, called switches, has an index of 0. I’ve noted this fact in a comment so that when you revisit the code later, you will know what’s going on. Depending on which segment is selected, you hide or show the appropriate controls .

Before you run the application, let’s apply a small tweak to make it look a little better. With iOS 7, Apple introduced some new GUI paradigms. One of these is that the status bar at the top of the screen is transparent so that your content shines right through it. Right now, that yellow Apress icon really sticks out like a sore thumb against your app’s white background, so let’s extend that yellow color to cover your entire view. In Main.storyboard, select the main content view (it’s labeled View in the Document Outline) and press ⌥⌘4 to bring up the Attributes Inspector. Click in the color swatch labeled Background (which currently contains a white rectangle) to open the standard OS X color picker. One feature of this color picker is that it lets you choose any color you see on the screen. With the color picker open, click the dropper icon at the bottom right to open a magnifying glass. Drag the magnifying glass over the Apress image view in the storyboard and click when it’s over a yellow part of the image. You should now see the background color of the Apress image in the color picker next to the dropper icon in the color picker. To set it as the background color for the main content view, select the main view in the Document Outline and then click the yellow color in the color picker. When you’re done, close the color picker.

On the screen, you may find that the background and the Apress image seem to have slightly different colors, but when run in the simulator or on a device, they will be the same. These colors appear to be different in Interface Builder because macOS automatically adapts colors depending on the display you’re using. On an iOS device and in the simulator, that doesn’t happen.

Run the app. You’ll see that the yellow color fills the entire screen, with no visible distinction between the status bar and your app’s content. If you don’t have full-screen scrolling content or other content that requires the use of a navigation bar or other controls at the top of the screen, this can be a nice way to show full-screen content that isn’t interrupted by the status bar quite as much.

If you’ve typed everything correctly, you should also be able to switch between the button and the pair of switches using the segmented control. And if you tap either switch , the other one will change its value as well. The button, however, still doesn’t do anything. Before you implement it, you need to talk about action sheets and alerts.

Implementing the Action Sheet and Alert

Action sheets and alerts are both used to provide the user with feedback .

  • Action sheets are used to force the user to make a choice between two or more items. On iPhones, the action sheet comes up from the bottom of the screen and displays a series of buttons (see Figure 4-3). On the iPad, you specify the position of the action sheet relative to another view, typically a button. Users are unable to continue using the application until they have tapped one of the buttons. Action sheets are often used to confirm a potentially dangerous or irreversible action, such as deleting an object .

  • Alerts appear as rounded rectangles in the middle of the screen (see Figure 4-4). Like action sheets, alerts force users to respond before they are allowed to continue using the application. Alerts are usually used to inform the user that something important or out of the ordinary has occurred. Like action sheets, alerts may be presented with only a single button, although you have the option of presenting multiple buttons if more than one response is appropriate.

Note

A view that forces users to make a choice before they are allowed to continue using their application is known as a modal view.

Displaying an Action Sheet

Switch to ViewController.swift and you’ll implement the button’s action method . Begin by looking for the empty onButtonPressed() method that Xcode created for you and then add the code in Listing 4-7 to create and show the action sheet.

Listing 4-7. Displaying the Action Sheet
@IBAction func onButtonPressed(_ sender: UIButton) {
    let controller = UIAlertController(title: "Are You Sure?",
                                       message:nil, preferredStyle: .actionSheet)


    let yesAction = UIAlertAction(title: "Yes, I'm sure!",
                                  style: .destructive, handler: { action in
                                    let msg = self.nameField.text!.isEmpty
                                        ? "You can breathe easy, everything went OK."
                                        : "You can breathe easy, (self.nameField.text),"
                                        + "everything went OK."
                                    let controller2 = UIAlertController(
                                        title:"Something Was Done",
                                        message: msg, preferredStyle: .alert)
                                    let cancelAction = UIAlertAction(title: "Phew!",
                                                                      style: .cancel, handler: nil)
                                    controller2.addAction(cancelAction)
                                    self.present(controller2, animated: true,
                                                               completion: nil)
    })


    let noAction = UIAlertAction(title: "No way!",
                       style: .cancel, handler: nil)


    controller.addAction(yesAction)
    controller.addAction(noAction)


    if let ppc = controller.popoverPresentationController {
        ppc.sourceView = sender
        ppc.sourceRect = sender.bounds
    }


    present(controller, animated: true, completion: nil)
}

What exactly did you do there? Well, first, in the onButtonPressed() action method , you allocated and initialized a UIAlertController, which is a view controller subclass that can display either an action sheet or an alert.

let controller = UIAlertController(title: "Are You Sure?",
                        message:nil, preferredStyle: .actionSheet)

The first parameter is the title to be displayed. Refer to Figure 4-3 to see how the title you’re supplying will be displayed at the top of the action sheet. The second parameter is a message that will be displayed immediately below the title, in a smaller font. For this example, you’re not using the message, so you supply the value nil for this parameter. The final parameter specifies whether you want the controller to display an alert (value UIAlertControllerStyle.alert) or an action sheet (UIAlertControllerStyle.actionSheet). Since you need an action sheet, you supply the value UIAlertControllerStyle.actionSheet here.

The alert controller does not supply any buttons by default—you have to create a UIAlertAction object for each button that you want and add it to the controller. Listing 4-8 shows the part of the code that creates the two buttons for your action sheet .

Listing 4-8. Creating the Action Sheet Buttons
let yesAction = UIAlertAction(title: "Yes, I'm sure!",
               style: .destructive, handler: { action in
             // Code omitted – see below.
        })


let noAction = UIAlertAction(title: "No way!",
               style: .cancel, handler: nil)

For each button, you specify the title, the style, and a handler to be called when the button is pressed. There are three possible styles to choose from.

  • UIAlertActionStyle.destructive should be used when the button triggers a destructive, dangerous, or irreversible action, such as deleting or overwriting a file. The title for a button with this style is drawn in red in a bold font .

  • UIAlertActionStyle.default is used for a normal button, such as an OK button, when the action that will be triggered is not destructive. The title is drawn in a regular blue font.

  • UIAlertStyle.cancel is used for the Cancel button. The title is drawn in a bold blue font.

Finally, you add the buttons to the controller.

[controller addAction:yesAction];
[controller addAction:noAction];

To make the alert or action sheet visible, you need to ask the current view controller to present the alert controller. Listing 4-9 shows how you present an action sheet.

Listing 4-9. Presenting an Action Sheet
if let ppc = controller.popoverPresentationController {
     ppc.sourceView = sender
     ppc.sourceRect = sender.bounds
}    
present(controller, animated: true, completion: nil)

The first four lines configure where the action sheet will appear by getting the alert controller’s popover presentation controller and setting its sourceView and sourceRect properties. I’ll say more about these properties shortly. Finally, you make the action sheet visible by calling your view controller’s present (_:animated:completion:) method, passing it the alert controller as the controller to be presented. When a view controller is presented, its view temporarily replaces that of the view controller that’s presenting it. In the case of the alert view controller, the action sheet or alert partially covers the presenting view controller’s view; the rest of the view is covered by a dark, translucent background that lets you see the underlying view but makes it clear that you can’t interact with it until you dismiss the presented view controller .

Now let’s revisit the popover presentation controller configuration. On the iPhone, the action sheet always pops up from the bottom of the screen, as shown in Figure 4-3, but on the iPad, it’s displayed in a popover—a small, rounded rectangle with an arrow that points toward another view, usually the one that caused it to appear. Figure 4-29 shows how your action sheet looks on the iPad Air simulator.

A329781_4_En_4_Fig29_HTML.jpg
Figure 4-29. Action sheet presented on an iPad Air

As you can see, the popover’s arrow points to the Do Something button. That’s because you set the sourceView property of the alert controller’s popover presentation controller to point to that button and its sourceRect property to the button’s bounds, as shown in Listing 4-10.

Listing 4-10. Setting the sourceView and sourceRect Properties
if let ppc = controller.popoverPresentationController {
    ppc.sourceView = sender
    ppc.sourceRect = sender.bounds
}

Notice the if let construction—this is necessary because on the iPhone, the alert controller does not present the action sheet in a popover, so its popoverPresentationController property is nil.

In Figure 4-29, the popover appears below the source button, but you can change this, if you need to, by setting the popover presentation controller’s permittedArrowDirections property, which is a mask of permitted directions for the popover’s arrow. The following code moves the popover above the source button by setting this property to UIPopoverArrowDirection.down, as shown in Listing 4-11.

Listing 4-11. Setting the Direction of the Popover
if let ppc = controller.popoverPresentationController {
        ppc.sourceView = sender
        ppc.sourceRect = sender.bounds
        ppc.permittedArrowDirections = .down
}

If you compare Figure 4-29 and Figure 4-3, you’ll see that the No Way! button is missing on the iPad. The alert controller does not use buttons with style UIAlertStyle.cancel on the iPad because users are accustomed to dismissing a popover without taking any action by tapping anywhere outside of it.

Presenting an Alert

When the user presses the Yes, I’m Sure! button, you want to pop up an alert with a message . When a button that was added to an alert controller is pressed, the action sheet (or alert) is dismissed, and the button’s handler block is called with a reference to the UIAlertAction from which the button was created. The code that’s executed when the Yes, I’m Sure! button is pressed is shown in Listing 4-12.

Listing 4-12. Popping Up the Alert Message
let yesAction = UIAlertAction(title: "Yes, I'm sure!",
                             style: .destructive, handler: { action in
                             let msg = self.nameField.text!.isEmpty
                                            ? "You can breathe easy, everything went OK."
                                            : "You can breathe easy, (self.nameField.text),"
                                            + "everything went OK."
                             let controller2 = UIAlertController(
                                            title:"Something Was Done",
                                            message: msg, preferredStyle: .alert)
                              let cancelAction = UIAlertAction(title: "Phew!",
                                                                style: .cancel, handler: nil)
                              controller2.addAction(cancelAction)
                              self.present(controller2, animated: true,
                                                             completion: nil)
})

The first thing you do in the handler block is create a new string that will be displayed to the user. In a real application, this is where you would do whatever processing the user requested. You’re just going to pretend you did something and notify the user by using an alert. If the user has entered a name in the top text field, you’ll grab that, and you’ll use it in the message that you’ll display in the alert . Otherwise, you’ll just craft a generic message to show.

let msg = self.nameField.text!.isEmpty
        ? "You can breathe easy, everything went OK."
        : "You can breathe easy, (self.nameField.text),"
          + " everything went OK."

The next few lines of code are going to look kind of familiar. Alert views and action sheets are created and used in a similar manner. You always start by creating a UIAlertController.

let controller2 = UIAlertController(
                title:"Something Was Done",
                message: msg, preferredStyle: .alert)

Again, you pass a title to be displayed. This time, you also pass a more detailed message, which is that string you just created. The final parameter is the style, which you set to UIAlertControllerStyle.alert because you want an alert, not an action sheet. Next, you create a UIAlertAction for the alert’s cancel button and add it to the controller.

let cancelAction = UIAlertAction(title: "Phew!",
                           style: .cancel, handler: nil)
controller2.addAction(cancelAction)

Finally, you make the alert appear by presenting the alert view controller.

self.present(controller2, animated: true, completion: nil)

You can see the alert that’s created by this code in Figure 4-4. You’ll notice that your code does not attempt to get and configure the alert controller’s popover presentation controller . That’s because alerts appear in a small, rounded view in the center of the screen on both iPhone and iPad, so there is no popover presentation controller to configure.

Save ViewController.swift and then build, run, and try out the completed application.

Summary

In this lengthy chapter, I hope I didn’t hit you with too much new stuff, but I went through the use of a good number of controls and showed many different implementation details. You got more practice with outlets and actions, saw how to use the hierarchical nature of views to your advantage, and got some more practice adding Auto Layout constraints. You learned about control states and stretchable images. You also learned how to use both action sheets and alerts.

There’s a lot going on in your Control Fun application. Feel free to go back and try things out. Change values, experiment by adding and modifying code, and see what different settings in Interface Builder do. There’s no way I could take you through every permutation of every control available in iOS, but the application you just put together is a good starting point and covers a lot of the basics.

In the next chapter, you’re going to look at what happens when the user rotates an iOS device from portrait to landscape orientation or vice versa. You’re probably well aware that many apps change their displays based on the way the user is holding the device, and I’m going to show you how to do that in your own applications.

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

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