Hour 12. Making Choices with Toolbars and Pickers


What You’ll Learn in This Hour:

Image The use of toolbars and pickers in iOS application interfaces

Image How to implement the date picker object

Image Ways to customize the display of a picker view

Image The relationship between pickers, toolbars, and popovers


In this hour, we continue multiview application development in our tutorials, but our primary focus is on two new user interface elements: toolbars and pickers. Toolbars present a set of common functions in a static bar at the top or bottom of the screen. A picker is a unique user interface (UI) element that both presents information to users and collects their input.

Whereas toolbars are similar to any other graphical UI (GUI) element, pickers aren’t implemented through a single method; they require several. This means that our tutorial code is becoming a bit more complex, but nothing you can’t handle. We need to work fast to fit this in an hour, so we better get started now.

Understanding the Role of Toolbars

Toolbars (UIToolbar) are, comparatively speaking, one of the simpler UI elements that you have at your disposal. A toolbar is implemented as a slightly translucent bar, either at the top or bottom of the display (see Figure 12.1), with buttons (UIBarButtonItem) that correspond to actions that can be performed in the current view. The buttons provide a single selector action, which works nearly identically to the typical Touch Up Inside event that you’ve used with UIButtons numerous times.

Image

FIGURE 12.1 Toolbars are a prevalent part of iOS application interfaces.

Toolbars, as their name implies, are used for providing a set of choices to the user to perform functions on the content within the main view. They aren’t intended for changing between completely different application interfaces; for that, you want to implement a tab bar, and that’s in the next hour’s lesson. Toolbars can be created almost entirely visually and are the de facto standard for triggering the display of a popover on the iPad. To add a toolbar to a view, open the Object Library and search for “toolbar.” Drag the toolbar object to the top or bottom of your view; iPhone applications usually leave the toolbar on the bottom.

You might imagine that toolbars would be implemented similarly to a segmented control, but the controls on the toolbar are entirely independent objects. An instance of a UIToolbar is nothing more than a gray bar across your screen. For a toolbar to do something, it needs a button.

Bar Button Items

If I were naming a button that gets added to a toolbar, I’d call it a toolbar button. Apple named it a bar button item (UIBarButtonItem). Regardless of its name, bar button items are the interactive elements that make a toolbar do something besides look like a stripe on your iOS device’s screen. The iOS Library provides three bar button objects, as shown in Figure 12.2. Although these may appear to be independent objects, they’re really a single thing: an instance of a bar button item. Bar button items can be customized with over a dozen common system button types or set to any arbitrary text or image.

Image

FIGURE 12.2 Three configurations of a single object.

To add a bar button to a toolbar, drag a bar button item into the toolbar in your view. The bar button items will appear as children of the toolbar within the document outline area. Double-clicking the name on a button enables editing, just like a standard UIButton. You can also use the handle on the side of the button to increase its size. What you can’t do, however, is drag the button around in the bar.

To position buttons, you need to insert special bar button items into the toolbar: flexible and fixed spaces. Flexible spaces expand to fill all possible available space between the buttons on either side of it (or the sides of the toolbar). For example, to position a button in the center, you add flexible spaces on either side of it. To position two buttons on either side of the toolbar, a single flexible space between them gets the job done. Fixed spaces are exactly what they sound like: a fixed width that can be inserted before or after existing buttons.

Bar Button Attributes

To configure the appearance of any bar button item, select it and open the Attributes Inspector (Option-Command-4), shown in Figure 12.3. You have three styles to choose from: Plain (just text), Bordered (the same as Plain in iOS 7 and 8), and Done (bold text). In addition, you can set several “identifiers.” These are common button icons/labels that help your toolbar buttons match Apple’s iOS application standards—including flexible and fixed space identifiers that will make your bar button item behave as either of these two special button types.

Image

FIGURE 12.3 Configure the bar button items.

If none of the standard button styles work for you, you can set an image to use as a button. The image should be a PNG between 20×20 and 25×25 points. Solid colors are automatically restyled into the toolbar tint, so don’t worry about trying to color-match your bar item text.

Exploring Pickers

Because we’re dedicating a good portion of an hour to pickers (UIPickerView), you can probably surmise that they’re not quite the same as the other UI objects that we’ve been using. Pickers are a unique feature of iOS. They present a series of multivalue options in a clever spinning interface—often compared to a slot machine. Rather than fruit or numbers, the segments, known as components, display rows of values that the user can choose from. The closest desktop equivalent is a set of pop-up menus. Figure 12.4 displays the standard date picker (UIDatePicker).

Image

FIGURE 12.4 The picker offers a unique interface for choosing a sequence of different, but usually related, values.

Pickers should be used when a user needs to make a selection between multiple (usually related) values. They are often used for setting dates and times but can be customized to handle just about any selection option that you can come up with.


Note

In Hour 9, “Using Advanced Interface Objects and Views,” you learned about the segmented control, which presents the user with multiple options in a single UI element. The segmented control, however, returns a single user selection to your application. A picker can return several values from multiple user selections—all within a single interface.


Apple recognized that pickers are a great option for choosing dates and times, so it has made them available in two different forms: date pickers, which are easy to implement and dedicated to handling dates and times; and custom picker views, which you can configure to display as many components as rows as you want—using whatever data you want.

Date Pickers

The date picker (UIDatePicker), shown in Figure 12.5, is very similar to the other objects that we’ve been using over the past few hours. To use it, we add it to a view, connect an action to its Value Changed event, and then read the returned value. Instead of returning a string or integer, the date picker returns an NSDate object. The NSDate class is used to store and manipulate what Apple describes as a “single point in time” (in other words, a date and time).

Image

FIGURE 12.5 Configure the appearance of the date picker in the Attributes Inspector.

To access the NSDate represented by a UIDatePicker instance, you use its date variable property. Pretty straightforward, don’t you think? In our example project, we implement a date picker and then retrieve the result, perform some date arithmetic, and display the results in a custom format.

Date Picker Attributes

Like many GUI objects, the date picker can be customized using the Attributes Inspector. For example, the picker can be configured to display in one of four different modes:

Image Date and Time: Shows options for choosing both a date and a time

Image Time: Shows only times

Image Date: Shows only dates

Image Count Down Timer: Displays a clock-like interface for choosing a duration

You can set the locale for the picker, which determines the ordering of the different components. In addition, you can configure the interval between dates and times, set the default date/time that is displayed, and set date/time constraints to help focus the user’s choices.


Tip

The Date attribute is automatically set to the Current (the current date and time) when you add the control to the view. You can also set the Date drop-down to Custom and choose your own date and time for the preset value.


Picker Views

Picker views (UIPickerView) are similar in appearance to date pickers but have an almost entirely different implementation. In a picker view, the only thing that is defined for you is the overall behavior and general appearance of the control; the number of components and the content of each component are entirely up to you. Figure 12.6 demonstrates a picker view that includes two components with images and text displayed in their rows.

Image

FIGURE 12.6 Picker views can be configured to display anything you want.

A custom picker is added to your application using the IB editor; just drag a picker view from the Object Library into your view. Unfortunately, a custom picker view’s appearance is not configured in the Attributes Inspector. Instead, you need to write code that conforms to two protocols—one that will provide the technical layout of the picker (the data source), and another that provides the information it will contain (the delegate). You can use the Connections Inspector to connect the delegate and data source outlets to a class in IB, or you can set these in code. Let’s review a simple implementation of these protocols before writing a real project.

The Picker View Data Source Protocol

The picker view data source protocol (UIPickerViewDataSource) includes methods that describe how much information the picker will be displaying:

Image numberOfComponentsInPickerView: Returns the number of components (spinning segments) needed in the picker.

Image pickerView:numberOfRowsInComponent: Given a specific component, this method is required to return the number of rows (different input values) in the component.

There’s not much to it. As long as we create these two methods and return a meaningful number from each, we’ll successfully conform to the picker view data source protocol. For example, if I want to create a custom picker that shows a total of two columns, with one selection value in the first, and two in the second, I can implement the protocol as shown in Listing 12.1.

LISTING 12.1 Implementing a Custom Picker Data Source Protocol


 1: func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
 2:     return 2
 3: }
 4:
 5: func pickerView(pickerView: UIPickerView,
 6:         numberOfRowsInComponent component: Int) -> Int {
 7:     if component==0 {
 8:         return 1
 9:     } else {
10:         return 2
11:     }
12: }


Lines 1–3 implement the numberOfComponentsInPickerView method, which returns 2—so the picker will have two components (that is, two little spinny wheels).

Lines 5–12 handle the pickerView:numberOfRowsInComponent method. When the component specified by iOS is 0 (this is the first component in the picker), the method returns 1 (line 8), meaning that there will be one label displayed in the wheel. When the component is 1 (the second component in the picker), the method returns 2 (line 10)—so there will be two possible options displayed to the user.

Obviously, a picker with components that have one or two possible values isn’t very useful—and part of the fun of using a picker is giving the user a UI element that he can flick around. This does, however, make it possible to demonstrate a custom picker without having to fill 10 pages with code.

Once the data source protocol is implemented, we still have one protocol (the picker view delegate protocol) between us and a working picker view.

The Picker View Delegate Protocol

The delegate protocol (UIPickerViewDelegate) takes care of the real work in creating and using a picker. It is responsible for passing the appropriate data to the picker for display and for determining when the user has made a choice. We can use a few protocol methods to make the delegate work the way we want, but again, only two are required:

Image pickerView:titleForRow:forComponent: Given a component and row number, this method must return the title for the row—that is, the string that should be displayed to the user.

Image pickerView:didSelectRow:inComponent: This delegate method will be called when the user makes a selection in the picker view. It is passed a row number that corresponds to a user’s choice, as well as the component that the user was last touching.


Note

If you check the documentation for the UIPickerViewDelegate protocol, you’ll notice that really all the delegate methods are optional—but unless we implement at least these two, the picker view isn’t going to be able to display anything or respond to a user’s selection.


To continue our example of a two-component picker (the first component with one value, the second with two), let’s implement the pickerView:titleForRow:forComponent method so that the picker shows Good in the first component, and Night and Day as the values in the second. Listing 12.2 demonstrates a simple picker view delegate protocol implementation.

LISTING 12.2 Implementing a Custom Picker Delegate Protocol


 1: func pickerView(pickerView: UIPickerView,
 2:     titleForRow row: Int, forComponent component: Int) -> String! {
 3:
 4:     if component == 0 {
 5:         return "Good"
 6:     } else {
 7:         if row == 0  {
 8:             return "Day"
 9:         } else {
10:             return "Night"
11:         }
12:     }
13: }
14:
15: func pickerView(pickerView: UIPickerView, didSelectRow row: Int,
16:         inComponent component: Int) {
17:     if component == 0 {
18:         // User selected an item in the first component.
19:     } else {
20:         // The user selected an item in the second component
21:         if row == 0 {
22:             // The user selected the string "Day"
23:         } else {
24:             // The user selected the string "Night"
25:         }
26:     }
27: }


Lines 1–13 provide the custom picker view with the label it should display for the component and row passed to the method. The first component (component 0) will only ever say Good, so line 4 checks to see whether the component parameter is 0, and, if it is, returns the string "Good".

Lines 6–12 handle the second component. Because it can show two values, the code needs to check the incoming row parameter to see which one we need to provide a label for. If the row is 0 (line 7), the code returns the string "Day" (line 8). If the row is 1, "Night" is returned (line 10).

Lines 15–27 implement the pickerView:didSelectRow:inComponent method. This is an exact mirror of the code that provides the values to be displayed in the picker, but instead of returning strings, the purpose of this method is to react to the user’s choice in the picker. I’ve added comments where you’d normally add your logic.

As you can see, coding the picker’s protocols isn’t something terribly complicated—it takes a few methods, but there are only a couple lines of code.

Advanced Picker Delegate Methods

You can include several additional methods in your implementation of a picker view’s delegate protocol that will further customize the appearance of the picker. We use the following three in this hour’s project:

Image pickerView:rowHeightForComponent: Given a component, this method returns the height of the row in points.

Image pickerView:widthForComponent: For a given component, this method should return the width of the component in points.

Image pickerView:viewForRow:viewForComponent:ReusingView: For a given component and row, return a custom view that will be displayed in the picker.

The first two methods are self-explanatory; if you want to change the height or width of a component or row in the picker, implement these methods to return the proper size in points. The third method is more involved (and for good reason): It enables a developer to completely change the appearance of what is displayed in a picker.

The pickerView:viewForRow:viewForComponent:ReusingView method takes a row and component and returns a view that contains custom content, such as images. This method overrides the pickerView:titleForRow:forComponent. In other words, if you use pickerView:viewForRow:viewForComponent:ReusingView for anything in your custom picker, you have to use it for everything.

As a quick (impractical and hypothetical) example, suppose that we want to present the Good / Day / Night picker as the row text in the first component and two asset library graphics (night, day) for the rows in the second. We would first get rid of the pickerView:titleForRow:forComponent method and then implement pickerView:viewForRow:viewForComponent:ReusingView. Listing 12.3 shows one possible implementation.

LISTING 12.3 Presenting the Picker with Custom Views


 1: func pickerView(pickerView: UIPickerView, viewForRow row: Int,
 2:     forComponent component: Int, reusingView view: UIView!) -> UIView {
 3:
 4:     if component == 0 {
 5:         // return a label
 6:         let goodLabel: UILabel = UILabel(frame: CGRectMake(0,0,75,32))
 7:         goodLabel.backgroundColor = UIColor.clearColor()
 8:         goodLabel.text = "Good"
 9:         return goodLabel
10:     } else {
11:         if row == 0 {
12:             // return day image view
13:             return UIImageView(image: UIImage(named: "day"))
14:         } else {
15:             // return night image view
16:             return UIImageView(image: UIImage(named: "night"))
17:         }
18:     }
19: }


The custom view logic begins on line 4, where it checks to see what component it is being “asked” about. If it is the first component (0), it should display Good in the UI. Because this method is required to return a UIView, returning "Good" isn’t a viable option. We can, however, initialize and configure a UILabel. Line 6 declares and initializes a label with a rectangle 75 points wide and 32 points high. Line 7 changes the background color to transparent (clearColor), and line 8 sets the text of the UILabel object to "Good".

The fully configured label is returned in line 9.

In the event that the method is queried for the second component (1), lines 10–18 are executed. Here, the row parameter is checked to determine whether it is being asked for the day (row 0) or night (row 1). For row 0, Lines 13 allocates a UIImageView object with an asset library image resource named day. Similarly, lines 20–21 create a UIImageView from the night image resource that is returned if the row parameter is 1.

It’s tutorial time. We first ease into pickers with a quick date picker example and then move on to implementing a custom picker view and its associated protocols.

Using the Date Picker

In the first tutorial, we implement a date picker that is displayed from a bar button item centered in a toolbar. On the iPhone, the picker should be shown via a modal segue. On the iPad, however, the picker must appear in a popover, as required by Apple’s human interface guidelines. Using the adaptive popover segue that you learned about in the last hour, we can write one piece of code and it will handle everything! (Yes, we’re going to slyly build another universal application.)

After the user chooses a date, the modal view/popover disappears, and a message is displayed that shows the calculated number of days between the current date and the date selected in the picker, as demonstrated in Figure 12.7.

Image

FIGURE 12.7 Display a date picker and use the chosen date in a calculation.

Implementation Overview

We build this project, named DateCalc, using the Single View Application template and following much of the same pattern as we did in the preceding hour’s lesson. Our initial scene contains an output label for the date calculation, along with a toolbar and button. Touching the button triggers a popover presentation segue to another scene. This second scene contains the date picker and a button to dismiss the view.

We’ll use the presentingViewController variable property that we learned about in the last lesson to access the initial view controller from the modal/popover view. All in all, the code will be quite straightforward with most of the difficult work being the date calculation itself.

Setting Up the Project

Create a new single-view application named DateCalc. Unlike previous projects, you should set the Devices drop-down to Universal.

The initial scene/view controller that is created with the template will contain the date calculation logic, but we need to add another scene and view controller that will be used to display the date picker interface.


Note: Universal Practice

The logic and UI layout for this hour’s projects is simple enough that “going universal” will save us some time, and give us a little bit more practice with tools you will learn about later in Hour 16, “Building Responsive User Interfaces,” and Hour 23, “Building Universal Applications.” You can do quite a bit more to customize the interfaces of universal applications, so don’t think that setting a drop-down is all it takes!


Adding the Date Chooser View Controller Class

To handle the display and selection of a date using the date picker, we add a class called DateChooserViewController to our project. Click the + button at the bottom-left corner of the project navigator and choose New File. When prompted, choose the iOS Source category and the Cocoa Touch class, and then click Next. When asked to name the class, enter DateChooserViewController and choose a subclass of UIViewController. Make sure that the language is set to Swift. On the last setup screen, choose your main project code group from the Group pop-up menu, and then click Create.

Next, create an instance of the DateChooserViewController in the Main.storyboard file.

Adding the Date Chooser Scene and Associating the View Controller

Open the Main.storyboard in the IB editor. Unlike other hours where we change the simulated size, we’ll work with the default little generic squares—because this will be a universal application. If you’d prefer to set a simulated size, you can, but it isn’t necessary.

Display the Object Library (Control-Option-Command-3) and drag a view controller into an empty area of the IB editor (or into the document outline area). Your project should now show two scenes.

Associate the new view controller with the DateChooserViewController class by first selecting the View Controller icon in the second scene within the document outline. Use the Identity Inspector (Option-Command-3) to set the Custom Class drop-down menu to DateChooserViewController.

Select the view controller object for the first scene and make sure the Identity Inspector is still onscreen. Within the Document section of the Identity Inspector, set the label for the first view to Initial. Repeat for the second scene, setting its view controller label to Date Chooser. The document outline will now display Initial Scene and Date Chooser Scene, as shown in Figure 12.8.

Image

FIGURE 12.8 Set up your initial and date chooser scenes.

Planning the Variables and Connections

Not too many outlets and actions are required in today’s projects. Let’s start with the initial scene, handled by the ViewController class. We have a label that is used for output, represented by a variable property we’ll call outputLabel. In addition, the ViewController class will have a method for calculating the difference between the current date and chosen date (calculateDateDifference).

For the date chooser scene, implemented in the DateChooserViewController class, there are two actions: setDateTime, called when the user selects a date in the date picker; and dismissDateChooser, used to exit the date chooser scene when a button in the view is touched.

Designing the Interface

To design the interface, follow these steps:

1. Open the Main.storyboard file and scroll so that you can see the initial scene in the editor.

2. Using the Object Library (Control-Command-Option-3), drag a toolbar into the bottom of the view.

3. By default, the toolbar contains a single button named item. Double-click the item title and change it to read Choose a Date.

4. Drag two Flexible Space Button Bar Items from the Object Library and position one on each side of the Choose a Date button. This forces the button into the center of the toolbar.

5. Add a label to the center of the view.

6. Use the Attributes Inspector (Option-Command-4) to increase the default text size for the label (if desired), center it, and set it to accommodate at least five lines of text.

7. Change the default text to read No Date Selected.

Because this is a universal application, it must be modified to support differing screen sizes. To do this, we’ll add constraints, just as we did in Hour 11, “Implementing Multiple Scenes and Popovers.” Constraints define how interface objects behave under different situations.

1. Select the toolbar to begin. You may want to use the entry within the document outline to make sure that you’re selecting it and not one of the buttons. Choose Editor, Pin, Leading Space to Superview. Repeat, choosing Pin, Trailing Space to Superview, then, finally, Pin, Bottom Space to Superview. This ensures that the right and left sides stretch to the sides of the screen and the bottom stays firmly planted on the bottom of the screen.

2. Next, select the label in the center of the screen and choose Editor, Align, Horizontal Center in Container. Repeat this, choosing Editor, Align, Vertical Center in Container the second time. This adds constraints to the label to keep it centered.

3. To make sure that everything is where it should be, choose Editor, Resolve Auto Layout Issues, Update Frames. (This option appears in the menu twice. Make sure that you select it from the All Views in View Controller heading.) If this option is grayed out, everything already is positioned properly.

Figure 12.9 shows my final view, with constraints.

Image

FIGURE 12.9 The initial scene.

Now, focus on the date chooser scene. For my design, I began by selecting the view and setting its background color to a light beige. This, of course, is unnecessary, but it does provide an additional visual cue to the user that the interface has changed. Drag a date picker into the top third of the view.

Above the date picker, drag a label and change its text to read Please Pick a Date. As a final step, drag a button to the bottom center of the view. This is used to dismiss the date chooser scene. Label the button Done.

As with the initial scene, the date picker scene must also have constraints added. Here’s how to add them:

1. Select the top label. Choose Editor, Pin, Top Space to Superview, then Editor, Align, Horizontal Center in Container. This keeps the label at the proper spacing from the top of the view, while centering it horizontal—regardless of screen size.

2. Next up, the picker itself. This object must stretch to the sides of the view, and maintain the same position from the top. Choose Editor, Pin, Leading Space to Superview. Repeat, choosing Pin, Trailing Space to Superview, then, finally, Pin, Top Space to Superview.

3. Finally, the Done button. This is the same as the top label, but must maintain its spacing from the bottom of the screen. Choose Editor, Pin, Bottom Space to Superview, then Editor, Align, Horizontal Center in Container.

4. To fix any errors, choose Editor, Resolve Auto Layout Issues, Update Frames. (This option appears in the menu twice. Make sure that you select it from the All Views in View Controller heading.) If this option is grayed out, everything already is positioned properly.

Figure 12.10 shows my finished date chooser interface.

Image

FIGURE 12.10 The date chooser scene.

Creating the Segue

Control-drag from the Choose bar button item in the initial scene to the view controller in the date chooser scene; you can do this either directly in the document outline area or using the visual representations of the scenes in the IB editor. When prompted for the storyboard segue type, choose Popover Presentation. A line labeled Popover presentation segue to Date Chooser appears in the initial scene within the document outline.

Creating and Connecting the Outlets and Actions

Between our two scenes, we have three connections to make: an outlet in the initial scene, and two actions in the date chooser scene. Let’s review those now:

Image outputLabel (UILabel): The label that will display the results of the date calculation in the initial scene.

Image dismissDateChooser: An action method triggered by the Done button in the date chooser scene.

Image setDateTime: An action method invoked when the date picker changes its value.

Switch to the assistant editor, and begin by wiring up the initial view’s outlet.

Adding the Outlet

Select the output label in the initial scene and Control-drag from the label to just below the class line in ViewController.swift. When prompted, create a new outlet named outputLabel, as shown in Figure 12.11.

Image

FIGURE 12.11 Connect to the output label.

Adding the Actions

Beyond the single outlet, every other connection in this project is an action.

Move to the second (date chooser) scene and Control-drag from the date picker to the DateChooserViewController.swift file, targeting below the class line. When prompted, create a new action named setDateTime that is triggered on the Value Changed event. Next, control-drag from the Done button to DateChooserViewController.swift. Create a new action called dismissDateChooser that will be triggered from the button.

Implementing the Date Calculation Logic

With the interface complete, the most difficult work that we have in front of us with the date picker implementation is writing the calculateDateDifference logic. To do what we’ve set out to (show the difference between today’s date and the date in the picker), we must complete several tasks:

Image Get today’s date

Image Display a date and time

Image Calculate the difference between two dates

Before writing the code, let’s look at the different methods and data types that we need.

Getting the Date

To get the current date and store it in an NSDate object, all that we need to do is to initialize a new NSDate. When initialized, it automatically stores the current date. This means that a single line takes care of our first hurdle:

let todaysDate: NSDate = NSDate()

Displaying a Date and Time

Unfortunately, displaying a date and time is a bit more tricky than getting the current date. Because we’re going to be displaying the output in a label, we already know how it is going to be shown on the screen, so the question is really, how do we format a string with an NSDate object?

Interestingly enough, there’s a class to handle this for us. We’ll create and initialize an NSDateFormatter object. Next, we set object’s setdateFormat variable property to create a custom format using a pattern string. Finally, we apply that format to our date using another method of NSDateFormatter, stringFromDate—which, given an NSDate, returns a string in the format that we defined.

For example, if we assume that we’ve already stored an NSDate in a variable todaysDate, we can output in a format like Month, Day, Year Hour:Minute:Second(AM or PM) with these lines:

let dateFormat: NSDateFormatter = NSDateFormatter()
dateFormat.dateFormat = "MMMM d, yyyy hh:mm:ssa"
let todaysDateString: String = dateFormat.stringFromDate(todaysDate)

First, dateFormat is initialized in a new object of the type NSDateFormatter. Then the string "MMMM d, YYYY hh:mm:ssa" is used to set the format internally in the object. Finally, a new string is returned and stored in todaysDateString by using the dateFormat object’s instance method stringFromDate.

Determining the Difference Between Two Dates

The last thing that we need to understand is how to compute the difference between two dates. Instead of needing any complicated math, we can just use the timeIntervalSinceDate instance method in an NSDate object. This method returns the difference between two dates, in seconds. For example, if we have two NSDate objects, todaysDate and futureDate, we could calculate the time in seconds between them with this:

let difference: NSTimeInterval = todaysDate.timeIntervalSinceDate(futureDate)


Note

Notice that we store the result in a variable of type NSTimeInterval. This isn’t an object. Internally, it is just a double-precision floating-point number. Apple abstracts this from us by using a new type of NSTimeInterval so that we know exactly what to expect out of a date difference calculation, but we can work with it just like any other floating-point number.


Note that if the timeIntervalSinceDate: method is given a date before the object that is invoking the method (that is, if futureDate was before todaysDate in the example), the difference returned is negative; otherwise, it is positive. To get rid of the negative sign, we use the function fabs(<float>) that, given a floating-point number, returns its absolute value.

Implementing the Date Calculation and Display

To calculate the difference and dates, we implement a method in ViewController.swift called calculateDateDifference that receives a single parameter (chosenDate). After writing the method for the calculation, we add code to the date chooser view controller to call the calculation when the date picker is used.

Add the calculateDateDifference implementation from Listing 12.4 to your ViewController.swift file now.

LISTING 12.4 Calculating the Difference Between Two Dates


 1: func calculateDateDifference(chosenDate: NSDate) {
 2:     let todaysDate: NSDate = NSDate()
 3:     let difference: NSTimeInterval =
 4:         todaysDate.timeIntervalSinceDate(chosenDate) / 86400
 5:
 6:     NSLog("%@",NSDate().timeIntervalSinceDate(NSDate.distantFuture() as NSDate))
 7:
 8:     let dateFormat: NSDateFormatter = NSDateFormatter()
 9:     dateFormat.dateFormat = "MMMM d, yyyy hh:mm:ssa"
10:
11:     let todaysDateString: String = dateFormat.stringFromDate(todaysDate)
12:     let chosenDateString: String = dateFormat.stringFromDate(chosenDate)
13:
14:     let differenceOutput: String = NSString(format:
15:         "Difference between chosen date (%@) and today (%@) in days: %1.2f",
16:         chosenDateString, todaysDateString, fabs(difference))
17:
18:     outputLabel.text=differenceOutput
19: }


Much of this should look pretty familiar based on the preceding examples, but let’s review the logic. Lines 2 and 3–4 do most of the work we set out to accomplish.

In line 2, we initialize todaysDate as a new NSDate object. This automatically stores the current date and time in the object. In lines 3–4, we use timeIntervalSinceDate to calculate the time, in seconds, between todaysDate and chosenDate (the date selected by the Date Picker object).

The result is divided by 86400 and stored in the difference constant. Why 86400? This is the number of seconds in a day, so we will be able to display the number of days between dates, rather than seconds.

In lines 8–12, we create a new date formatter object (NSDateFormatter) and use it to format todaysDate and chosenDate, storing the results in todaysDateString and chosenDateString.

Lines 14–16 format the final output string by creating a new string (differenceOutput) and initializing it with stringWithFormat. The format string provided includes the message to be displayed to the user as well as the placeholders %@ and %1.2f—representing a string and a floating-point number with a leading zero and two decimal places. These placeholders are replaced with the todaysDateString, chosenDateString, and the absolute value of the difference between the dates, fabs(difference). I chose to use stringWithFormat to create the output, rather than string interpolation, because you can’t format numbers directly with string interpolation.

In line 18, the label we added to the view, differenceResult, is updated to display differenceOutput.

Updating the Date Output

To finish the calculation object, we need to add code to call the calculateDateDifference method so that the display is updated when the user picks a date. There are actually two places we need to call the calculation: when the user picks a new date and when date chooser is first displayed. In the second case, the user hasn’t picked a date and the current date is displayed in the picker.

Start with the most important use case: handling a user’s action by calculating the date difference when the setDateTime method is called. Recall that this is triggered when the date picker value changes. Update the method stub in DateChooserViewController.swift with the code in Listing 12.5.

LISTING 12.5 Calculating the Date Difference


@IBAction func setDateTime(sender: AnyObject) {
    (presentingViewController as
        ViewController).calculateDateDifference((sender as UIDatePicker).date)
}


The presentingViewController variable property is used to access the calculateDateDifference method in ViewController.swift. We pass it the date returned from the date picker, and we’re done. Unfortunately, if the user exits the picker without explicitly making a choice, there won’t be a date calculation displayed.

If the user exits the picker, we can assume that the current date is what the user wanted. To handle this implicit selection, add the viewDidAppear method in DateChooserViewController.swift, as shown in Listing 12.6.

LISTING 12.6 Performing a Default Calculation When the Date Chooser Is First Displayed


override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    (presentingViewController as ViewController).calculateDateDifference(NSDate())
}


This is identical to the setDateTime method, but we pass it a new date object with the current date rather than querying the picker. This ensures that a calculated value is displayed, even if the user immediately dismisses the modal scene or popover.

Believe it or not, we’re just about finished with the application. Our remaining task is dealing with some loose ends regarding the popover presentation segue—specifically, dismissing it, and sizing the popover itself for large screen devices.


Try it Yourself: Time for a Playdate

If you have any questions about manipulating dates and times, the playground is a great place to test out NSDate and NSDateFormatter methods. With that introductory sentence out of the way, take a few moments to sit in awe of that heading I just wrote... “Time for a Playdate”... It incorporates, Time, Date, and part of Playground. I’m so very pleased with myself right now.

The reason we’re here, however, is to try out some of the NSDate and NSDateFormatter logic so you can get an idea of a few of the nice features provided by these classes.

Create a new iOS playground, then input the following code:

import UIKit

let currentDate: NSDate = NSDate()

let dateFormat: NSDateFormatter = NSDateFormatter()
dateFormat.dateFormat = "MM/dd/yy hh:mm a"


currentDate.earlierDate(myDate)
currentDate.laterDate(myDate)

As we’ve seen in the Date Picker application, this creates a new object currentDate that contains the current date and time, and an NSDateFormatter object in dateFormat. Unlike the application we’ve built, however, the formatter can be used to create a new date from a String!

For example, add this line to the end of the code block:

let myDate: NSDate = dateFormat.dateFromString("12/25/1970 10:00 AM")!

In the margin on the right, you’ll see that myDate is now an NSDate object, that contains a date and time based on the string I provided.

If you wanted to know which of the two dates we have (myDate or currentDate) is earlier or later, you can use the NSDate instance methods earlierDate and laterDate. These, given another date, return the date that is earlier or later, respectively. Add these lines to see the logic in action:

currentDate.earlierDate(myDate)
currentDate.laterDate(myDate)

The first of the two lines returns the contents of myDate, since it is earlier, while the second returns the date in currentDate. You could also write this as:

myDate.earlierDate(currentDate)
myDate.laterDate(currentDate)

The method doesn’t care which of the two objects is being tested it always returns the correct result.

To see the number of seconds between the two dates, add in this line:

currentDate.timeIntervalSinceDate(myDate)

For even more fun, you can divide this result by 60 to see the result in minutes, 3600 for hours, 86400 for days, and so on:

currentDate.timeIntervalSinceDate(myDate) / 60
currentDate.timeIntervalSinceDate(myDate) / 3600
currentDate.timeIntervalSinceDate(myDate) / 86400
currentDate.timeIntervalSinceDate(myDate) / 31536000

I highly recommend playing around with the methods in these two classes to see what they can do. Date/Time manipulation is a traditionally difficult thing to code, but with the Cocoa classes Apple provides, you can work calendar magic without breaking a sweat.


Implementing the Scene Segue Logic

We want to dismiss the modal/popover view when the user presses the Done button in the date chooser scene. You’ve already made the connection to dismissDateChooser; you just need to add a call to dismissViewControllerAnimated:completion. Listing 12.7 shows the appropriate one-line implementation of dismissDateChooser in DateChooserViewController.swift.

LISTING 12.7 Dismissing the Modal Scene


@IBAction func dismissDateChooser(sender: AnyObject) {
    dismissViewControllerAnimated(true, completion: nil)
}



Note

In the preceding hour, we used an exit/unwind segue to move back to the initial scene; it was meant as a learning exercise. In this implementation, we just use the dismissViewControllerAnimated:completion method, because it is easier and quicker for a two-scene project.


The application should now work exactly as we intend, but the popover probably will be a bit larger that needed on the iPad. To limit the size of the popover to something manageable, we need to set the preferredContentSize variable property of the view controller that manages the popover’s content—that is, DateChooserViewController. Update viewDidLoad in DateChooserViewController.swift, as shown in Listing 12.8.

LISTING 12.8 Set a Preferred Size for the Popover


override func viewDidLoad() {
    super.viewDidLoad()
    preferredContentSize = CGSizeMake(340,380)
}


This code sets a preferred size for the content of 340 points wide by 380 points tall. You may want to play around with the sizes after you run the app. These looked good to me based on my interface design.

Building the Application

The date picker application is complete. Run and test the app to get an idea for how the different pieces come together. Something to look for during your testing is the behavior of the application with the date choose is shown in a popover (that is, when you run it on an iPad). When an application displays a popover, the initial scene remains visible while the popover is displayed. In this example, the result is that the user can actually see live feedback in the initial scene as he picks new dates in the popover.

You’ve just made a toolbar, implemented a date picker, learned how to perform some basic date arithmetic, and even formatted dates for output using date formatting strings. What could be better? Creating your own custom picker with your own data, of course.

Using a Custom Picker

In the second project of this hour’s lesson, you create a custom picker that presents two components: one with an image (an animal) and another with a text string (an animal sound). As with the previous project, the picker is displayed via a popover presentation segue—which results in a modal view on the iPhone and a popover on larger screened devices (usually just the iPad), as shown in Figure 12.12.

Image

FIGURE 12.12 Create a working custom picker.

When users choose an animal or sound from the custom picker view, their selection is displayed in an output label.

Implementation Overview

While the implementation of the custom picker requires that we have a class that conforms to the picker’s delegate and data source protocols, many of the core processes in this application are identical to the last. An initial scene contains an output label and a toolbar with a single button. Touching the button triggers a segue to the custom chooser’s scene. From there, the user can manipulate the custom picker and return to the initial scene by touching a Done button. iPad users can also touch outside of the popover.

The variable names will change a bit to better reflect their role in this application, but logic and implementation are nearly identical to the last. Because of this similarity, we will work quickly and provide detailed instructions only where the projects differ.

Setting Up the Project

Create a new project named CustomPicker based on the Single View Application template. Again, make sure you set the Devices drop-down to Universal—because we’ll be adding constraints to make it run nicely on all our devices.

Adding the Image Resources

For the custom picker to show animal pictures, we need to add a few images to the project. Because we know upfront that we need these images, we can add them to the project immediately. Open the project group in the project navigator area of Xcode. Click the main Images.xcassets file to open the project’s image assets. Now, drag the Images folder from the Hour’s Projects folder in the Finder into the left-hand column inside the asset catalog.

Open the Images group that appears in the catalog and verify that you have seven image assets in your project: bear.png, cat.png, dog.png, goose.png, mouse.png, pig.png, and snake.png.

Adding the Animal Chooser View Controller Class

Like the DateChooserViewController class presented a scene with a date picker, the AnimalChooserViewController class will handle the display and selection of an animal and a sound. Click the + button at the bottom-left corner of the project navigator and choose New File. Create a new Cocoa Touch class: a UIViewController subclass named AnimalChooserViewController using the Swift language. Save the new class files in your project’s main code group.

Adding the Animal Chooser Scene and Associating the View Controller

Open the Main.storyboard and display the Object Library (Control-Option-Command-3). Drag a view controller into an empty area of the IB editor or the document outline area.

Select the new scene’s view controller icon, and then use the Identity Inspector (Option-Command-3) to set the Custom Class drop-down menu to AnimalChooserViewController. Use the Identity Inspector’s Document section to set the label for the first view to Initial and the second scene to Animal Chooser. These changes should be reflected immediately in the document outline.

Planning the Variables and Connections

The outlets and actions in this project mirror the last, with one exception. In the previous tutorial, we needed a method to be executed when the date picker changed its value. In this tutorial, we implement protocols for the custom picker that include a method that is called automatically when the picker is used.

The ViewController class’s initial scene has a label for output (outputLabel). The class also shows the selected animal/sound using a method we’ll call from the second scene: displayAnimal:withSound:fromComponent.

The animal chooser scene is implemented in the AnimalChooserViewController class. It features one action (dismissAnimalChooser) used to exit the animal chooser scene, and six methods to handle the custom picker data source and delegate protocols. Finally, there are three variable properties (animalNames, animalSounds, and animalImages) referencing Array objects. These contain the animal names that we are displaying, the sounds to display in the custom chooser components, and the image resource names that correspond to the animals.

Adding Custom Picker Component Constants

When creating custom pickers, we must implement a variety of protocol methods that refer to the various components (the spinny wheels) by number. To simplify a customer picker implementation, you can define constants for the components so that you can refer to them symbolically.

In this tutorial, we refer to component 0 as the animal component and component 1 as the sound component. By defining a few constants at the start of our code, we can easily refer to them by name. Edit AnimalChooserViewController.swift and add these lines so that they follow the class line:

let kComponentCount: Int = 2
let kAnimalComponent: Int = 0
let kSoundComponent: Int = 1

The first constant, kComponentCount, is just the number of components that we want to display in the picker, whereas the other two constants, kAnimalComponent and kSoundComponent, can be used to refer to the different components in the picker without resorting to using their actual numbers.

Designing the Interface

Open the Main.storyboard file and scroll so that you can see the initial scene in the editor. Using the Object Library (Control-Command-Option-3), drag a toolbar into the bottom of the view. Change the default bar button item to read Choose an Animal and Sound. Use two Flexible Space Button Bar Items from the Object Library to center the button.

Next, add a label with the default text Nothing Selected to the center of the view. Use the Attributes Inspector to center the text, increase the font size, and set the label to display at least five lines of text.

As with the previous project, you’ll also need to add constraints so that the project displays properly on iPhone and iPad devices. Refer to the earlier instructions if you want additional details on what these changes do:

1. Select the toolbar. Choose Editor, Pin, Leading Space to Superview. Repeat, choosing Pin, Trailing Space to Superview, then, finally, Pin, Bottom Space to Superview.

2. Select the label in the center of the screen and choose Editor, Align, Horizontal Center in Container. Repeat this, choosing Editor, Align, Vertical Center in Container the second time.

3. Choose Editor, Resolve Auto Layout Issues, Update Frames. (This option appears in the menu twice. Make sure that you select it from the All Views in View Controller heading.) If this option is grayed out, everything is positioned properly and there’s no need to execute this option.

Figure 12.13 demonstrates my initial view layout.

Image

FIGURE 12.13 The initial scene.

Configure the animal chooser scene as you did the date chooser scene, setting a background label that reads Please Pick an Animal and Sound, but this time drag a picker view object into the scene. Add a button to the bottom of the view and label it Done. This, as before, is used to dismiss the animal chooser scene.

Same deal as before with the constraints:

1. Select the top label. Choose Editor, Pin, Top Space to Superview, then Editor, Align, Horizontal Center in Container.

2. Select the custom picker itself. Choose Editor, Pin, Leading Space to Superview. Repeat, choosing Pin, Trailing Space to Superview, then, finally, Pin, Top Space to Superview.

3. Next, the Done button. Choose Editor, Pin, Bottom Space to Superview, then Editor, Align, Horizontal Center in Container.

4. To fix any errors, choose Editor, Resolve Auto Layout Issues, Update Frames. (This option appears in the menu twice. Make sure that you select it from the All Views in View Controller heading.) If this option is grayed out, everything already is positioned properly.

Figure 12.14 shows my finished animal chooser interface.

Image

FIGURE 12.14 The animal chooser scene.

Setting the Picker View Data Source and Delegate

In this project, we have the AnimalChooserViewController class serve double-duty and act as the picker view’s data source and delegate. In other words, the AnimalChooserViewController class is responsible for implementing all the methods needed to make a custom picker view work.

To set the data source and delegate for the picker view, select it in the animal chooser scene or the document outline area, and then open the Connections Inspector (Option-Command-6). Drag from the dataSource outlet to the Animal Chooser View Controller line in the document outline. Do the same for the delegate outlet. Once finished, the Connections Inspector should resemble Figure 12.15.

Image

FIGURE 12.15 Connect the picker view’s delegate and dataSource outlets to the animal chooser view controller object.

Creating the Segue

Control-drag from the Choose an Animal button in the initial scene to the view controller in the animal chooser. Create a modal segue for the iPhone or popover for the iPad. A line labeled “Popover presentation segue to Animal Chooser View Controller” will show when the segue has been created.

Creating and Connecting the Outlets and Actions

A total of just two connections are required (an outlet in the initial scene, and a single action in the animal chooser scene):

Image outputLabel (UILabel): The label in the initial scene that will display the results of the user’s interactions with the picker view.

Image dismissAnimalChooser: An action method triggered by the Done button in the animal chooser scene.

Switch to the assistant editor and make the connections.

Adding the Outlet

Select the output label in the initial scene and Control-drag from the label to just below the class line in ViewController.swift. When prompted, create a new outlet named outputLabel.

Adding the Action

Move to the second scene and Control-drag from the Done button to AnimalChooserViewController.swift, targeting just below the constants you added earlier. Create a new action called dismissAnimalChooser that will be triggered from the button.

Implementing the Custom Picker View

Early in this hour, we presented a possible implementation of a very (very) limited custom picker view. Even though it didn’t represent a real-world application, it is close to what we will need to do to finish this example and create a custom picker that displays images and text, side by side, in two components. We’ll slow things down again and complete this hour’s lesson with a full explanation of the creation of the custom picker view.

Loading the Picker Data

To present the picker, we need to supply it with data. We’ve loaded the image resources, but to provide the images to the picker, we need to be able to reference them by name. In addition, we need to be able to “translate” between the image of an animal and its real name. That is, if a user picks an image of a pig, we want the application to say Pig, not pig.png. To do this, we have an array of animal images (animalImages) and an array of animal names (animalNames) that share the same index. For example, if the user picks an image that corresponds to the third element of animalImages, we can get the name from the third element of animalNames. We also need the data for the list of animal sounds presented in the second picker view component. For that, we use a third array: animalSounds.

Declare these three arrays as variable properties in AnimalChooserViewController.swift by updating the code at the top of the file to include these new Array declarations after the existing constants:

var animalNames: [String] = []
var animalSounds: [String] = []
var animalImages: [UIImageView] = []

Now, we need to initialize the data in each array. For the names and sounds arrays, we will just be storing strings. In the images array, however, we will be storing initialized UIImageViews. Update the viewDidLoad method in AnimalChooserViewController.swift, as shown in Listing 12.9.

LISTING 12.9 Loading the Data Required for the Picker View


 1: override func viewDidLoad() {
 2:     super.viewDidLoad()
 3:
 4:     animalNames=["Mouse","Goose","Cat","Dog","Snake","Bear","Pig"]
 5:     animalSounds=["Oink","Rawr","Ssss","Roof","Meow","Honk","Squeak"]
 6:     animalImages=[
 7:         UIImageView(image: UIImage(named: "mouse.png")),
 8:         UIImageView(image: UIImage(named: "goose.png")),
 9:         UIImageView(image: UIImage(named: "cat.png")),
10:         UIImageView(image: UIImage(named: "dog.png")),
11:         UIImageView(image: UIImage(named: "snake.png")),
12:         UIImageView(image: UIImage(named: "bear.png")),
13:         UIImageView(image: UIImage(named: "pig.png")),
14:     ]
15: }


Line 4 initializes the animalNames array with seven animal names.

Line 5 initializes the animalSounds array with seven animal sounds.

Lines 6–14 populate the animalImages array with seven UIImageView instances loaded from the images that were imported at the start of the project.

Implementing the Picker View Data Source Protocol

The next step is to begin implementing the protocols that the custom picker requires. The first, the data source protocol, provides information to the picker about the number of components it will be displaying and the number of elements within each of those components.

Declare that we will be conforming to the UIPickerViewDataSource by editing AnimalChooserViewController.swift and modifying the class line to read as follows:

class AnimalChooserViewController: UIViewController, UIPickerViewDataSource {

Next, implement the numberOfComponentsInPickerView method in AnimalChooserViewController.swift. This method returns the number of components the picker will display. Because we defined a constant for this (kComponentCount), all we need to do is return the constant, as shown in Listing 12.10.

LISTING 12.10 Returning the Number of Components


func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
    return kComponentCount
}


The other data source method required is the pickerView:numberOfRowsInComponent, which, given a component number, returns the number of elements that will be shown in that component. We can use the kAnimalComponent and kSoundComponent to simplify identifying which component is which, and the Array method count to get the number of elements in an array. Using this, we can implement pickerView:numberOfRowsInComponent using the approach in Listing 12.11.

LISTING 12.11 Returning the Number of Elements per Component


1: func pickerView(pickerView: UIPickerView,
2:         numberOfRowsInComponent component: Int) -> Int {
3:     if component==kAnimalComponent {
4:         return animalNames.count
5:     } else {
6:         return animalSounds.count
7:     }
8: }


Line 3 checks whether the component being queried is the animal component. If it is, line 4 returns a count of the number of animals in the animalNames array. (The image array would work as well.)

If the component being checked isn’t the animal component, we can assume it is the sound component (line 5) and return the count of elements in the animalSounds array (line 6).

That’s all the data source needs to do. The remainder of the picker view work is handled by the picker view delegate protocol: UIPickerViewDelegate.

Implementing the Picker View Delegate Protocol

The picker view delegate protocol handles customizing the display of the picker and reacting to a user’s choice within the custom picker. Update AnimalChooserViewController.swift to state our intention to conform to the delegate protocol:

class AnimalChooserViewController: UIViewController,
            UIPickerViewDataSource, UIPickerViewDelegate {

Several delegate methods are needed to produce the picker we want, but the most important is pickerView:viewForRow:forComponent:reusingView. This method takes an incoming component and row and returns a custom view that will be displayed in the picker.

For our implementation, we want the animal images to be returned for the first component, and a label with the animal sounds returned for the second. Add the method, as written in Listing 12.12, to your project.

LISTING 12.12 Providing a Custom View for Each Possible Picker Element


 1: func pickerView(pickerView: UIPickerView, viewForRow row: Int,
 2:     forComponent component: Int, reusingView view: UIView!) -> UIView {
 3:
 4:     if component==kAnimalComponent {
 5:         let chosenImageView: UIImageView = animalImages[row]
 6:         let workaroundImageView: UIImageView =
 7:             UIImageView(frame: chosenImageView.frame)
 8:         workaroundImageView.backgroundColor =
 9:             UIColor(patternImage: chosenImageView.image!)
10:         return workaroundImageView
11:     } else {
12:         let soundLabel: UILabel = UILabel(frame: CGRectMake(0,0,100,32))
13:         soundLabel.backgroundColor = UIColor.clearColor()
14:         soundLabel.text = animalSounds[row]
15:         return soundLabel
16:     }
17: }


In lines 4–10, we check to see whether the component requested is the animal component, and if it is, we use the row parameter to return the appropriate UIImageView stored in the animalImages array. Wait. No we don’t. Returning the result of line 5 is all we should have to do to implement a custom picker view using an image, but it is broken in iOS 7 and 8. If you try to use this approach, your images will flicker in and out of view in the picker.

To get around the iOS problem, my approach is to take the image view we created in viewDidLoad and allocate an initialize a new UIImageView with the same size (lines 6-7). Next, I set the backgroundColor variable property to the image referenced by the original image view (lines 8–9), and then return this new image view in line 10. There is absolutely no reason why this should work and the original method fail, but it does.

After digesting that little nugget of crazy, we can move on to the rest of the code.

If the component parameter isn’t referring to the animal component, we need to return a UILabel with the appropriate referenced row from the animalSounds array. This is handled in lines 11–16.

In line 12, we declare a UILabel named soundLabel and initialize it with a frame. Remember from earlier hours that views define a rectangular area for the content that is displayed on the screen. To create the label, we need to define the rectangle of its frame. The CGRectMake function takes starting x,y values and a width and height. In this example, we’ve defined a rectangle that starts at 0,0 and is 100 points wide and 32 points tall.

Line 13 sets the background color attribute of the label to be transparent (UIColor.clearColor() returns a color object configured as transparent). If we leave this line out, the rectangle will not blend in with the background of the picker view.

Line 14 sets the text of the label to the string of the specified row in animalSounds.

Finally, line 15 returns the UILabel—ready for display.

Changing the Component and Row Sizes

If you were to run the application now, you’d see the custom picker, but it would look a bit squished. To adjust the size of components in the picker view, we can implement two more delegate methods: pickerView:rowHeightForComponent and pickerView:widthForComponent.

For this application example, some trial and error led me to determine that the animal component should be 75 points wide, while the sound component looks best at around 150 points.

Both components should use a constant row height of 55 points.

Translating this into code, implement both of these methods in AnimalChooserViewController.swift, as shown in Listing 12.13.

LISTING 12.13 Setting a Custom Height and Width for the Picker Components and Rows


func pickerView(pickerView: UIPickerView,
        rowHeightForComponent component: Int) -> CGFloat {
    return 55.0
}

func pickerView(pickerView: UIPickerView,
        widthForComponent component: Int) -> CGFloat {
    if component==kAnimalComponent {
        return 75.0
    } else {
        return 150.0
    }
}


Reacting to a Selection in the Picker View

In the date picker example, you connected the picker to an action method and used the Value Changed event to capture when they modified the picker. Custom pickers do not work this way. To grab a user’s selection from a custom picker, you must implement yet another delegate method: pickerView:didSelectRow:inComponent. This method provides the component and row where a selection was made.

Notice anything strange about that method? It gives us the component and row the user selected but doesn’t provide the status of the other components. To get the value of other components, we have to use the picker instance method selectedRowInComponent along with the component we’re interested in checking.

In this project, when the user makes a selection, we call the method displayAnimal:withSound:fromComponent to display the selection in the initial scene’s output label. We haven’t yet implemented this, so let’s do so now.

The implementation of the method in ViewController.swift should take the incoming parameter strings and display them in the output label. Nothing fancy required. My implementation is provided in Listing 12.14.

LISTING 12.14 Creating a Method to Display the User’s Selection


func displayAnimal(chosenAnimal: String, withSound chosenSound:String,
        fromComponent chosenComponent: String) {
    outputLabel.text =
      "You changed (chosenComponent) ((chosenAnimal) and the sound (chosenSound))"
}


The output label is set to the contents of the chosenComponent, chosenAnimal, and chosenSound strings. Unlike the previous project, we don’t need to deal with any number formatting, so we can use simple string interpolation to create the result.

Now that we have a mechanism for displaying the user’s choice, we need to handle their selection. Implement pickerView:didSelectRow:inComponent in the AnimalChooserViewController.swift file, as shown in Listing 12.15.

LISTING 12.15 Reacting to a User’s Selection


 1: func pickerView(pickerView: UIPickerView, didSelectRow row: Int,
 2:     inComponent component: Int) {
 3:
 4:     let initialView: ViewController = presentingViewController as ViewController
 5:
 6:     if component==kAnimalComponent {
 7:         let chosenSound: Int = pickerView.selectedRowInComponent(kSoundComponent)
 8:         initialView.displayAnimal(animalNames[row],
 9:             withSound: animalSounds[chosenSound], fromComponent: "the Animal")
10:     } else {
11:         let chosenAnimal: Int = pickerView.selectedRowInComponent(kAnimalComponent)
12:         initialView.displayAnimal(animalNames[chosenAnimal],
13:             withSound: animalSounds[row], fromComponent: "the Sound")
14:     }
15: }


The first thing that the method does is grab a handle to the initial scene’s view controller in lines 4. We need this so we can display the user’s selection within that scene.

Line 6 checks to see whether the selected component is the animal component. If it is, we still need to grab the currently selected sound (line 7). Lines 8–9 call the displayAnimal:withSound:fromComponent method we just wrote, passing it the animal name, using the incoming row to select the right value from the array, the currently selected sound, and a string (the Animal) to describe the component the user manipulated.

In the event that the user chose a sound, lines 10–14 are executed instead. In this case, we need to look up the currently selected animal (line 11), and then, once again, pass all the relevant values to the display method to get them onscreen for the user.

Handling an Implicit Selection

As was the case with the date picker, the user can display the custom picker and then dismiss it without choosing anything. In this case, we should assume that the user wanted to choose the default animal and sound. To make sure this is accounted for, as soon as the animal chooser scene is displayed, we can update the output label in the initial scene with the default animal name, sound, and a message that nothing has been selected from a component (“nothing yet...”).

As with the date picker, we can do this by adding the viewDidAppear method in AnimalChooserViewController.swift. Implement the method as shown in Listing 12.16.

LISTING 12.16 Setting a Default Selection


override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    let initialView: ViewController =
        presentingViewController as ViewController
    initialView.displayAnimal(animalNames[0],
        withSound: animalSounds[0], fromComponent: "nothing yet...")
}


The implementation is simple. It grabs the initial view controller, and then it uses it to call the display method, passing the first element of the animal names and sounds arrays (because those are the elements displayed first in the picker). For the component, it passes a string to indicate that the user hasn’t chosen anything from a component... yet.

Implementing the Scene Segue Logic

As with the previous tutorial, we still have two things wrap up. First, we must implement the dismissAnimalChooser method in AnimalChooserViewController.swift (see Listing 12.17) to get rid of the scene when the user touches Done.

LISTING 12.17 Dismissing the Modal Scene


@IBAction func dismissAnimalChooser(sender: AnyObject) {
    dismissViewControllerAnimated(true, completion: nil)
}


Second, we need to set a reasonable size for the popover to use when it actually is displayed as a popover (on the iPad, for example). To do this, we’ll set the preferredContentSize in the AnimalChooserViewController.swift file, as shown in Listing 12.18.

LISTING 12.18 Update AnimalChooserViewController’s viewDidLoad Method


override func viewDidLoad() {
    super.viewDidLoad()

    animalNames=["Mouse","Goose","Cat","Dog","Snake","Bear","Pig"]
    animalSounds=["Oink","Rawr","Ssss","Roof","Meow","Honk","Squeak"]
    animalImages=[
        UIImageView(image: UIImage(named: "mouse.png")),
        UIImageView(image: UIImage(named: "goose.png")),
        UIImageView(image: UIImage(named: "cat.png")),
        UIImageView(image: UIImage(named: "dog.png")),
        UIImageView(image: UIImage(named: "snake.png")),
        UIImageView(image: UIImage(named: "bear.png")),
        UIImageView(image: UIImage(named: "pig.png")),
    ]

    preferredContentSize = CGSizeMake(340,380)
}


That finishes the handling of the popover/modal view segue and all the logic needed to make sure that it works as intended.

Building the Application

Run the application and test your new custom picker view. The behavior of the animal chooser view controller should be nearly identical to the date picker despite the differences in implementation. When the application displays the picket in a popover, it updates the output label as soon as a selection is made. When running modally (on the iPhone), it does this as well, but the view is obscured by the modal scene.

Further Exploration

As you learned in this lesson, UIDatePicker and UIPickerView objects are reasonably easy to use and quite flexible in what they can do. There are a few interesting aspects of using these controls that we haven’t looked at that you may want to explore on your own. First, both classes implement a means of programmatically selecting a value and animating the picker components so that they “spin” to reach the values you’re selecting: setDate:animated and selectRow:inComponent:animated. If you’ve used applications that implement pickers, chances are, you’ve seen this in action.

Another popular approach to implementing pickers is to create components that appear to spin continuously (instead of reaching a start or stopping point). You may be surprised to learn that this is really just a programming trick. The most common way to implement this functionality is to use a picker view that simply repeats the same component rows over and over (thousands of times). This requires you to write the necessary logic in the delegate and data source protocol methods, but the overall effect is that the component rotates continuously.

Although these are certainly areas for exploration to expand your knowledge of pickers, you may also want to take a closer look at the documentation for toolbars (UIToolbar) and the NSDate class. Toolbars provide a clean way to create an unobtrusive user interface and save a great deal of space versus adding buttons everywhere in your views. The ability to manipulate dates can be a powerful capability in your applications.

Summary

In this hour’s lesson, you explored three UI elements—UIToolbar, UIDatePicker, and UIPickerView—that each present the user with a variety of options. Toolbars present a static list of buttons or icons at the top or bottom of the screen. Pickers present a “slot-machine” type of view where the user can spin components in the interface to create custom combinations of options.

Although toolbars and date pickers work like other UI elements you’ve experienced over the past few hours, the custom picker view is rather different. It requires us to write methods that conform to the UIPickerViewDelegate and UIPickerViewDataSource protocols.

In writing the sample picker applications, you also had a chance to make use of NSDate methods for calculating the interval between dates, as well as NSDateFormatter for creating user-friendly strings from an instance of NSDate. Although not the topic of this lesson, these are powerful tools for working with dates and times and interacting with your users.

Q&A

Q. Why didn’t you cover the timer mode of the UIDatePicker?

A. The timer mode doesn’t actually implement a timer; it’s just a view that can display timer information. To implement a timer, you actually need to track the time and update the view accordingly—not something we can easily cover in the span of an hour.

Q. Where did you get the method names and parameters for the UIPickerView protocols?

A. The protocol methods that we implemented were taken directly from the Apple Xcode documentation for UIPickerViewDelegate and UIPickerViewDataSource. If you check the documentation, you can just copy and paste from the method definitions into your code—or just start typing and let Xcode autocomplete the definitions for you.

Q. Should I try just returning a normal UIImageView for a custom picker, or assume I need a workaround?

A. Absolutely give it a try. The documentation for iOS 8 shows that the original method should work. It just didn’t work as intended at the time of this writing.

Workshop

Quiz

1. What class handles storing and manipulating dates and times?

a. NSTime

b. NSDateTime

c. NSDate

d. NSCron

2. You can control the size of a popover by setting what variable property within its view controller?

a. setContentSize

b. forcedContentSize

c. preferredContentSize

d. desiredContentSize

3. Toolbar buttons are called by what name in iOS?

a. Bar button items

b. Bar buttons

c. Buttons

d. Toolbar items

4. What class is used for formatting dates and times for output?

a. NSTimeFormatter

b. NSDateTimeFormatter

c. NSCronFormatter

d. NSDateFormatter

5. When using toolbars, what popover presentation controller variable property can be used to set the origin of the popover’s arrow?

a. barButtonItem

b. buttonItem

c. barItem

d. toolbarButtonItem

6. Which protocol is responsible for generating a picker view and reacting to picker selection?

a. UIPickerViewDataSource

b. UIPickerViewDelegate

c. pickerViewDelegate

d. UICustomPickerViewDelegate

7. To create a custom picker that only uses text (versus custom views), we could use which of the following methods?

a. pickerView:viewForRow:forComponent

b. pickerView:textForRow:forComponent

c. pickerView:titleForRow:forComponent

d. pickerView:labelForRow:forComponent

8. By default, a date picker added to your project shows what?

a. Nothing

b. A date one year in advance

c. The date and time the component was added to your project

d. The current date and time

9. On the iPad, Apple requires that popovers _________________.

a. Be sized to fit the screen

b. Appear in a popover

c. Not be used to select dates

d. Always use custom views

10. Picker components are referred to by an integer value where the first component is what?

a. 0

b. 1

c. –1

d. nil

Answers

1. C. The NSDate class is used to store and manipulate dates and times.

2. C. Setting the preferredContentSize for view controller will determine its size when displayed in a popover.

3. A. Toolbar buttons are referred to in iOS as bar button items.

4. D. The NSDateFormatter class is used to format dates and times for output.

5. A. You can use the barButtonItem property of a popover presentation controller to present the popover from a toolbar button.

6. B. The UIPickerViewDelegate protocol is responsible for generating custom picker views and reacting to a user’s touch.

7. C. Simple text-only custom pickers can generate the picker components using the pickerView:titleForRow:forComponent method.

8. D. By default, a date picker will show the current date and time—no programming required.

9. B. Apple requires that pickers displayed on an iPad always appear within a popover.

10. A. The first component in a picker is referred to by the integer 0.

Activities

1. Modify the projects in this chapter so that the secondary view controllers check to see whether they are running within a popover. Remove the Done button if they are.

2. Update the dateCalc project so that allows the selection of two dates via two pickers. You can do this using two pickers in a single scene, or you can branch to two distinct scenes.

3. Update the CustomPicker project so that the user is rewarded for matching an animal to its sound. This was the original tutorial, but it had to be shortened for space. Here’s a hint: The animal sounds are presented in the reverse order of the animals.

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

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