Channels ▼
RSS

Mobile

Improving Data Entry with UI Pickers in iOS


Apple calls the technique of loading an object when you need it "lazy loading." I prefer to call it on-demand loading, which better explains what is being done. The point to deferring the creation of an object until it is needed is to conserve memory, which is in short supply on iOS devices, particularly the early iPhone and iPod Touch models. If the temperature picker is not present in memory, then it is allocated, and a "Done" button is placed on the navigation bar. As the picker is allocated, the UIPickerViewDataSource protocols determine that the picker requires only one component, with the number of choices (rows) equal to the number of values in the setPointPickerViewArray.

If the picker is already visible (the user's last choice was a temperature setting and she hasn't dismissed the picker yet), then we do nothing. Otherwise, an animation is performed to show the picker sliding smoothly upward onto the screen and it is added as a subview (Listing Eight):

Listing Eight

	// Check if temperature picker is already on screen. If so,
	// skip creating picker view and go to handling choice
	if (self.tempPickerView.superview == nil)
	{
		// Move the row to center of screen
		[self.tableView scrollToRowAtIndexPath:moveIndexPath
		atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
			
		// Add the picker
		[self.view.window addSubview: self.tempPickerView];
			
		// size up the picker view to our screen and compute the
		// start/end frame origin for our slide up animation
		//
		// compute the start frame
		CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
		CGSize pickerSize =
			[self.tempPickerView   sizeThatFits:CGSizeZero];
		CGRect startRect = CGRectMake(0.0,
						screenRect.origin.y + screenRect.size.height,
			pickerSize.width, pickerSize.height);
		self.tempPickerView.frame = startRect;
			
		// compute the end frame
		 CGRect pickerRect = CGRectMake(0.0,
			screenRect.origin.y + screenRect.size.height -
				 pickerSize.height,
			pickerSize.width,
			pickerSize.height);
		// start the slide up animation
		[UIView beginAnimations:nil context:NULL];
		[UIView setAnimationDuration:PICKER_ANIMATION_DURATION];
		[UIView setAnimationDelay:PICKER_ANIMATION_DELAY];
		// Give time for the table to scroll before animating the
	  	// picker's appearance
			
	  	// we need to perform some post operations after the
		// animation is complete
      	[UIView setAnimationDelegate:self];
			
	}

After all of the safety checks, allocations, and animations, at long last we get to the code that presents the component's content (Listing Nine):

Listing Nine

    NSUInteger i;
    NSArray *charArray =
		[[NSArray alloc]
			initWithArray:[textField.text componentsSeparatedByString:@" "]];
    NSString *str = [charArray objectAtIndex:0];
    // Only using digits, without "F" or "C"
    for (i = 0; i < [setPointPickerViewArray count]; i++) {
    	if ([str isEqualToString:[setPointPickerViewArray objectAtIndex:i]]) {
			 break;
	  	}
    }
		
    [charArray release];
		
    // Update position on temperature picker
    [tempPickerView selectRow:i inComponent:0 animated:YES];
    [tempPickerView reloadComponent:0];
		
    return NO;
}

The code extracts the temperature string from the textField that was tapped on, then compares this string to those that make up the picker's component wheel. If a match is found, then the picker presents that value as the current choice. Finally, we return NO to inform the textField object that we are handling the editing process.

Code similar to this handles the creation and management of the time picker. For the complete details, see the sample thermostat code provided with this article.

Now, it's time to add the delegate code (Listing Ten);

Listing Ten

#pragma mark -
#pragma mark UIPickerViewDelegate

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row
inComponent:(NSInteger)component
{
	
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	NSUInteger tableRow = (pickerView.tag % 100) / 10;
	NSUInteger tableSection = (pickerView.tag / 100);
	
	NSUInteger inputField =
		  (pickerView.tag - (tableSection * 100) - (tableRow * 10));
	
    // don't show selection for the custom picker
	if (pickerView == tempPickerView) {
	    switch (inputField) {
	        case HEATING_FIELD:
			  ;
			  [settings.heatStr replaceObjectAtIndex:
			    ((tableSection * 4) + tableRow)withObject:
			    
[setPointPickerViewArray objectAtIndex:row]];
			  break;
		    case COOLING_FIELD:
			  ;
			  [settings.coolStr replaceObjectAtIndex:
			  ((tableSection * 4) + tableRow) withObject:
			  [setPointPickerViewArray objectAtIndex:row]];
			  break;
				
		    default:
			  break;
	}
		
	// Generate an indexPath to table row so that we update
	// only the specific row, rather than the entire table
	NSUInteger temperaturerIndex[] = {tableSection, tableRow};
	NSIndexPath *temperatureIndexPath =
	[[NSIndexPath alloc] initWithIndexes:temperaturerIndex length:2];
	NSArray *rowArray = [[NSArray alloc]
		  initWithObjects:temperatureIndexPath, nil];
	[self.tableView reloadRowsAtIndexPaths:rowArray
		  withRowAnimation:UITableViewRowAnimationNone];
		
	[rowArray release];
	[temperatureIndexPath release];
	[pool release];
}


// Set the size of the component wheel on the picker
- (CGFloat)pickerView:(UIPickerView *)pickerView
widthForComponent:(NSInteger)component
{
	CGFloat componentWidth = 60.0;
	return componentWidth;
}

// Populate the picker with temperatures
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
	NSString *returnStr = @"";
	
	// note: custom picker doesn't care about titles, it
	// uses custom views
	if (pickerView == tempPickerView) {
        returnStr = [NSString stringWithFormat:@"%@",
						  [setPointPickerViewArray objectAtIndex:row]];
	}	
	
	return returnStr;
}

This bit of code handles the user's choice on the temperature settings picker. The table's section and row is obtained from the view tag because they serve as the indexes into the table data arrays. The switch statement does the magic for updating the proper array element based on these indexes. Next, we make a temporary indexPath so that we can have reloadRowsAtIndexPaths refresh the contents of the one table row. The widthForComponent method returns the desired size of the picker in pixels (60). Finally, titleForRow returns a blank title for the picker, and a string that contains the picker's content. The content is obtained from the values in the setPointPickerViewArray.

Now let's add the code for the UIPickerView's data source protocols (Listing Eleven):

Listing Eleven

#pragma mark -
#pragma mark UIPickerViewDataSource

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
	return [setPointPickerViewArray count];
}

- (NSInteger)numberOfComponentsInPickerView:
	  (UIPickerView *)pickerView
{
	return 1;
}

The methods numberOfRowsInComponent and numberOfComponentsInPickerView provide the temperature picker component count and the number of rows in the component, as has been described earlier in this article.

When you build the app, you get errors for the constants PICKER_ANIMATION_DELAY and PICKER_ANIMAION_DURATION. We forgot to add those. Oops. So enter the following declarations into TableTempController.m:

#define PICKER_ANIMATION_DELAY	0.6
#define PICKER_ANIMATION_DURATION	0.3

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