Channels ▼
RSS

Mobile

Improving Data Entry with UI Pickers in iOS


Now the app should build and run. If it does not, check over the code that you've entered for errors or missing lines until you get a successful build. When you launch the app, the thermostat table appears as before. However, when you tap on a temperature field, instead of a keyboard, the temperature picker glides into position from the bottom of the screen (Figure 3). If you tap on a time, the time picker is displayed. For either choice, a blue "Done" button appears in the navigation bar. If you tap on other dates or times, the wheels rotate to display the value of the field you tapped on. This arrangement give you the capability to quickly change a set of times or temperatures using swiping motions rather than poking at a keyboard and dismissing it for each field. If you try to switch from times to temperatures or vice-versa, you get the warning dialog.

Figure 3: Temperature picker.

If you use the temperature picker to change a setting, the value changes in the table row. For the time picker, since UIDatePicker generates a control event rather than a message, when you change a time you get a crash. The app also crashes when you tap the "Done" button. This is expected behavior because we haven't written the methods to handle events from the time picker or the "Done" button. Let's do that now.

The action methods for the date picker appear in Listing Twelve:

Listing Twelve

- (IBAction)dateAction:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	// Fetch the tag in order to derive that column, row,
	// and textfield of the picker choice
	NSUInteger tag = timeView.tag;
	NSUInteger row = (tag % 100) / 10;
	NSUInteger section = (tag / 100);
	
	// Save the new choice in the settings
	[settings.timeOfDays replaceObjectAtIndex:
		 ((section * 4) + row) withObject:self.timeView.date];

	// Generate an indexPath to table row so that we update
	// only the specific row, rather than the entire table
	NSUInteger dateIndex[] = {section, row};
	NSIndexPath *dateIndexPath =
		 [[NSIndexPath alloc] initWithIndexes:dateIndex length:2];
	NSArray *rowArray =
		 [[NSArray alloc] initWithObjects:dateIndexPath, nil];
	[self.tableView reloadRowsAtIndexPaths:rowArray
		withRowAnimation:UITableViewRowAnimationNone];
	
	[rowArray release];
	[dateIndexPath release];
    [pool release];
}

- (IBAction)doneAction:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
		
	CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
	CGRect endFrame = self.timeView.frame;
	endFrame.origin.y = screenRect.origin.y + screenRect.size.height;
	
	// start the slide down animation
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:PICKER_ANIMATION_DURATION];
	
	// we need to perform some post operations after the
	// animation is complete
	[UIView setAnimationDelegate:self];
	[UIView setAnimationDidStopSelector:@selector(slideDownDidStop)];
	
	self.timeView.frame = endFrame;
	[UIView commitAnimations];
	
	// remove the "Done" button in the nav bar
	self.navigationItem.rightBarButtonItem = nil;
		
	[pool release];
}

- (void)slideDownDidStop
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	// the date picker has finished sliding downwards, so remove it
	[self.timeView removeFromSuperview];
	self.timeView = nil;
	[timeView release];
	
	[pool release];
}

The method dateAction handles the action message sent when a choice is made with the time picker. Like the temperature picker's didSelectRow method, dateAction gathers the table section and row indexes to update the corresponding element in the table array, timeOfDays. Then it refreshes the table row content.

The doneAction method fires when the user taps on the "Done" button. It simply sets up the animation that slides the picker view down off the screen. When the animation completes, it invokes the method slideDownDidStop to perform final clean up. Then the "Done" button is removed from the navigation bar. Similar methods, doneTempAction and tempSlideDownDidStop, perform the same animation and housekeeping duties for the temperature picker when it is dismissed. The code for these methods is available in the sample app.

Improving the User Experience

The addition of the pickers to manage changes to the time and temperature settings of the thermostat app improves the UX vastly. The pickers effectively constrain any time and temperature choices to valid ones, so the user never has to reenter a value. In addition, it only takes a few swipes and a tap to modify a setting. If the user changes several settings in succession (say, both the cooling setting and the heating setting on one row), the picker components rotate to the present the current value tapped on. Besides looking cool, the animation provides important visual feedback that indicates that the app is responding to the user. The quick and effective entry of the settings values is a big win for the mobile user.

It is also a big win for the programmer, too. As you've seen, the iOS APIs are very modular. We were able to completely modify the behavior of the setting cells in the table by just using a different UITextFieldmethod. We did not have to touch the table code at all, which means the table functions remain reliable.

If there's a negative, it took a lot of code to support the pickers and the animation. However, I think that you'll find the picker version of the thermostat app is much easier to use. Years ago, an Apple product manager told me that for the Mac OS U, the company "would throw as many processor cycles as necessary to make things better for the user." What we've done here is spend a few more processor cycles (and lines of code) to improve how the user interacts with the data in the table. That's something to keep in mind as you design the UI on your own iOS apps.


Tom Thompson is head of Proactive Support for embedded products at Freescale Semiconductor.


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