Channels ▼
RSS

Mobile

Improving Data Entry with UI Pickers in iOS


I recently wrote a DDJ article, Making Two-Way Tables in iOS, which explained how to display tables with editable text on an iPhone. I used the Cocoa Touch class, UITextField, to both present the table data and permit the user to edit it. The sample iOS app that accompanied the article — which was a mock-up of a thermostat controller — displays the temperature and time settings in several UITextFields (Figure 1). These editable fields allowed the user to tap and modify the values of each field. When the editing was done, the changes were saved back into the table and the screen was refreshed.

Figure 1: Table with editable fields in iOS.

That was well and good, and the user interface (UI) worked fine. Unfortunately, it leaves a lot to be desired in terms of the user experience (UX). For example, tapping on a UITextField in the table brings up a virtual keyboard to modify the text. However, there were no sanity checks as to whether the values entered are valid temperatures or times. And as we all know, at the best of times, feeding garbage data into a mobile app results in garbage out; and at the worst of times, the app corrupts a remote database, resulting in all sorts of mischief.

Limiting What Data is Entered

Obviously, the app needs to police the values entered into these fields. One way to do this is to restrict the range of text permitted by changing the type of keyboard that the UITextField presents. You can use the Objective-C property of the UITextInputTraits protocol, keyboardType, to specify a numeric keypad for data entry. That works for the cooling and heating temperature settings, whose values consist of only digits. However, it's unsuitable for the time setting. That's because the NSDateFormatter class, which converts the string into an NSDate object, requires not only digits but also a colon and either an AM or PM string to specify the time. You have to fall back on using the standard keyboard to enter a time value, and then write code that tests the validity of this value. If the entered value is bogus, you display an alert and have the user try again. Even the temperature setting is not immune to bad data entry: The user might enter a temperature that the central air system cannot support.

While this scheme prevents the entry of invalid settings, it's still awful as far as the UX goes. Mobile users need to enter data into an app quickly and accurately, all while on the go. Pecking at a virtual keyboard or keypad, dismissing an error dialog, and then trying to reenter the value again is going to frustrate mobile users and bring them to a screeching halt.

Is there a way to make entering these values fast yet accurate, and therefore improve the user experience? Fortunately, there is a Cocoa Touch class, UIPickerView, that fits the bill. UIPickerView displays a "picker" that consists of rollers or wheels that resemble those of a slot machine or odometer. The wheels present a range of values, and you pick a value (hence the class name) by sliding or flicking on the wheels until the desired value appears and then tapping a "Done" or similar button. Because you control the values the picker displays, the possibility of entering an invalid value is eliminated. In the Making Two-Way Tables in iOS article, I had mentioned that explaining how to use a picker to improve data entry into a table would require an article of its own. This is that article.

Picker Overview

UIPickerView displays a "generic" picker. The size, number of wheels, and the type of information presented (which can include images as well as strings) on the picker is customizable. There is also a UIDatePicker class whose specialty is user selection of a time or date. The Timer pane of the iOS Clock app is an example of the UIDatePicker. Since both pickers are visible objects, they inherit from the Coca Touch display class, UIView. (For more information on the iOS UIView class, consult the article, The iPhone Isn't Easy.) You might expect that UIDatePicker, being similar in function to UIPickerView, would use UIPickerView as its superclass, but that's not the case. UIPickerView inherits directly from UIView, while UIDatePicker inherits from UIControl, which then inherits from UIView.

The UIDatePicker

Let's start with the details on UIDatePicker, as it is easier to describe and set up. It uses a property, date, to display the time/date and save any changes when a choice is made with the picker. The date property consists of an NSDate object.

UIDatePicker has several properties that control the display of the date. For example, these properties specify the time zone used, the value of the minute intervals used in the display, and whether the picker functions as a countdown timer. The class has one lone instance method, setDate, which modifies the value in the date property. Changes to the picker display can be animated, so that the wheels rotate to display the digits and text that make up the time or date. When the user finishes rotating a wheel, the picker generates an action message that is sent to a delegate object. The delegate can then respond to the change in date or time. Since the message corresponds to a control event, UIControlEventValueChanged, this explains why UIDatePicker inherits from UIControl. Because the thermostat app uses NSDate to display the time settings, employing a UIDatePicker to edit the time values becomes straightforward.

The UIPickerView class is more flexible in its set up and in the type of content it displays. However, the price of the additional flexibility requires that there be more details to get right to properly configure the picker display (see Figure 2). Besides the UIPickerView class itself, there are also protocols defined for source information and delegate responses. (Recall that a Cocoa Touch protocol describes a set of defined methods that a delegate must implement.)

Figure 2: The UIPickerView and UIDatePicker classes.

The UIPickerView class provides a view that displays a picker per your specifications. Some of these include: the number of wheels (termed components), the rows of content for each component, and the screen dimensions that the view occupies. The class displays the rows of content on the components, and can also animate the components when their content changes. When the picker loads, it first uses the UIPickerViewDataSource protocol to obtain information from the designated source object about its layout. The source returns the number of components and the number of rows the picker needs through the required methods numberOfComponentsInPickerView and numberOfRowsInComponent, respectively. The picker uses the UIPickerViewDelegate protocol to obtain the dimensions of the components, and to supply the content for the rows in each component. The delegate object also implements a response when the user makes a choice on the picker. Note that when a choice is made, UIPickerView simply issues a message, and not a control event, which allows you greater flexibility in how the app handles it. The UIPickerViewDataSource methods, numberOfComponentsInPickerView and numberOfRowsInComponent, are similar in purpose to the UITableViewDataSource protocol methods numberOfSectionsInTableView and numberOfRowsInSection that return the number of sections and rows in a table. Furthermore, the UIPickerViewDelegate method didSelectRow, which handles a picker selection on the row of a component, is similar to the UITableViewDelegate protocol method didSelectRowAtIndexPath that indicates the choice of a table row. This option lets us apply our existing expertise with iOS tables to implement a picker that handles the temperature settings.

From Keyboard to Picker

Previously in the sample app, when you tapped on a UITextField and completed editing it with the keyboard, the delegate responded by invoking the textFieldShouldReturn method. The duty of this method is to determine which field was edited, save the text from the modified field into the appropriate element of the table data arrays, update the table display, and finally reassign the textField as being the first responder. To change this behavior, the UITextField delegate must be modified so that it uses a picker object rather than the usual editing process. This requires that the delegate implement a textFieldShouldBeginEditing method rather than a textFieldShouldReturn method. The textFieldShouldBeginEditing allows you to specify whether the default editing process should occur. This is where we will hijack the editing process.

For our purposes, textFieldShouldBeginEditing disallows the default text editing and invokes a picker instead. The method then adds the picker as a subview, which makes it the first responder. The picker deals with the user input until it is dismissed. Once dismissed, the code updates the table data, refreshes the table display and exits as it did before. One complication is that we'll have to set up the equivalent of a "Return" key to dismiss the picker.

Because the thermostat app presents times and temperatures, textFieldShouldBeginEditing uses both a UIDatePicker and a UIPickerView to handle the disparate data types.

This time we won't start from scratch writing the app code. Instead, we'll modify the previous two-way table app to change the editing behavior. While Interface Builder was used to assemble the table and its cells, this time we'll add the picker objects programmatically. This involves some slight changes to the TemperatureControlAppDelegate class, while the TableTempController class requires the addition of quite a few methods to implement and support the picker.

The purpose of the modifications to the TemperatureControlAppDelegate files are to add a UINavigationController to the app. Table displays on iOS often present a navigation bar, courtesy of a UINavigationController, that enables a user to drill down to the desired level of detail in the tables. However, for our sample app the navigation bar serves as a placeholder for a "Done" button that dismisses the picker.

To add the navigation bar, we modify TemperatureControlAppDelegate.h to declare a UINavigationController (Listing One):

Listing One

#import <UIKit/UIKit.h>
#import "TempTableController.h"

@interface TemperatureControlAppDelegate : NSObject <UIApplicationDelegate>
{
    UIWindow *window;
	  UINavigationController *navigationController;
	  TempTableController *table;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) UINavigationController *navigationController;
@property (nonatomic, retain) IBOutlet TempTableController *table;

@end

Moving on to TemperatureControlAppDelegate.m, in didFinishLaunchingWithOptions we create instances of UITableViewController and UINavigationController. As UINavigationController is allocated, the initWithRootViewController parameter attaches the instance of our custom UITableViewController, TempTableController, to it (Listing Two).

Listing Two

@synthesize navigationController;

    // Set up table view controller
    TempTableController *aViewController =
       [[TempTableController alloc]
          initWithNibName:NULL bundle:[NSBundle mainBundle]];
    UINavigationController *aNavigationController =
       [[UINavigationController alloc]
          initWithRootViewController:aViewController];
    self.navigationController = aNavigationController;
    [aNavigationController release];
    [aViewController release];
	
    // Add view managed by the login controller to main window
    [window addSubview:[navigationController view]];

After these modifications, when you build the app and run it, a blank navigation bar should appear at the top of the screen.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video