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:
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: