Table View Scrolling

Today I want to cover a small problem with the UITableView method scrollToRowAtIndexPath:atScrollPosition:animated. This method doesn’t work quite properly when called from an out-of-the-box UITableViewController on a navigation controller’s stack. Some special setup is needed.

Test Code

To demonstrate the problem and workaround, we’ll be using the following DemoTable class. It doesn’t do anything particularly interesting (it just displays a list of 20 numbers, of which one is checked at the beginning, and lets you move the checkmark around) but it does display the behavior we’re interested it.

//
//  DemoTable.h
//
//  Created by Michael Heyeck on 8/7/09.
//

#import <UIKit/UIKit.h>


@interface DemoTable : UITableViewController {
	NSUInteger	value;
}

- (id)initWithValue:(NSUInteger)a_value;

@end
//
//  DemoTable.m
//
//  Created by Michael Heyeck on 8/7/09.
//

#import "DemoTable.h"


@implementation DemoTable

// New D. I.
- (id)initWithValue:(NSUInteger)a_value
{
	if (self = [super initWithStyle:UITableViewStyleGrouped])
	{
		value = a_value;
		[[self tableView] reloadData];
		[[self tableView] scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:value inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
	}
	return self;
}


// Super's D. I.
- (id)initWithStyle:(UITableViewStyle)style
{
	return (self = [self initWithValue:0]);
}


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
	[super viewDidLoad];
	self.title = @"Demo Table";
}


/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}


- (void)dealloc {
    [super dealloc];
}

#pragma mark UITableViewDataSource methods

- (NSInteger)tableView:(UITableView*)tv numberOfRowsInSection:(NSInteger)section
{
	return 20;
}


- (UITableViewCell*)tableView:(UITableView*)tv cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	static NSString* cellId = @"CellId";

	UITableViewCell* cell = [tv dequeueReusableCellWithIdentifier:cellId];
	if (cell == nil)
	{
        	cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellId] autorelease];
	}

	cell.text = [NSString stringWithFormat:@"Option %d",indexPath.row];
	if (value == (indexPath.row))
	{
		cell.accessoryType = UITableViewCellAccessoryCheckmark;
	}
	else
	{
		cell.accessoryType = UITableViewCellAccessoryNone;
	}
	return cell;
}

#pragma mark UITableViewDelegate methods

- (void)tableView:(UITableView*)tv didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
	[tv deselectRowAtIndexPath:indexPath animated:NO];
	if (value != indexPath.row)
	{
		// Uncheck old row
		[tv cellForRowAtIndexPath:[NSIndexPath indexPathForRow:value inSection:0]].accessoryType = UITableViewCellAccessoryNone;
		
		// Check new row
		[tv cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
	}
}

@end

The DemoTable should be displayed with code like this:

[self.navigationController pushViewController:[[[DemoTable alloc] initWithValue:19] autorelease] animated:YES];

(executed from another view controller on a navigation controller’s stack).

The Problem

The scrollToRowAtIndexPath:atScrollPosition:animated problem can be seen by passing different values to initWithValue:. Here are the screens displayed for argument values of 0, 10, and 19, respectively:

w/arg 0 w/arg 10 w/arg 19

As you can see, the preselected row is not scrolled into view when it is near the end of the table.

Solution

The best fix I’ve found for this (so far) involves tweaking the UITableView frame. I use code like the following:

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
	[super viewDidLoad];
	self.title = @"Demo Table";

	// Needed to make scrollToRowAtIndexPath:atScrollPosition:animated: work properly near table edges
	static const NSUInteger navBarHeight = 44;
	CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
	self.tableView.frame = CGRectMake(0, navBarHeight, appFrame.size.width, appFrame.size.height-navBarHeight);
}

When we repeat the earlier tests, we get these, more satisfactory, results:

fixed w/arg 0 fixed w/arg 10 fixed w/arg 19
Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.

Comments are closed.