I have a project in which I need to display multiple UITableView
instances inside the same view on the iPad. They also happen to be rotated, but I'm fairly certain this is irrelevant. The user should be unaware that the view is made up of multiple table views. Therefore, I'd like to make it so that when I scroll one tableview, the others scroll with it at the same time.
Because UITableView
is a subclass of UIScrollView
, I figured I could handle UIScrollViewDelegate
methods and pass them along to all of the tableviews. Unfortunately, while I can capture some events, the method call granularity is not fine enough and I'm having trouble passing along these messages to the other tableviews. The closest I can get is implementing -scrollViewDidScroll:
and then calling -setContentOffset:animated
on every tableview. If I attempt to send this message for all potential cases, I end up locking up because -scrollViewDidScroll
is getting called for my -setContentOffset:animated
calls, so I end up freezing up. Regardless, if I eliminate the lockup by only using this method to detect scrolling on one tableview and then passing it on to the other tableviews, I find that while the other tableviews do end up scrolling to the same location, They lag behind a second or two.
Here's a sample project I created.
How can I implement this behavior without subclassing UITableView
?
You can get around the callback granularity issue by observing contentOffset
directly. In viewDidLoad
, set up KVO on each of the table views you need to synchronize:
for(UITableView *view in self.tableViewCollection)
{
[view addObserver:self
forKeyPath:@"contentOffset"
options:NSKeyValueObservingOptionNew
context:NULL];
}
Then, when you observe changes, temporarily unhook the observation, update the other table views' offsets, and turn observation back on.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
static BOOL isObservingContentOffsetChange = NO;
if([object isKindOfClass:[UITableView class]]
&& [keyPath isEqualToString:@"contentOffset"])
{
if(isObservingContentOffsetChange) return;
isObservingContentOffsetChange = YES;
for(UITableView *view in self.tableViewCollection)
{
if(view != object)
{
CGPoint offset =
[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue];
view.contentOffset = offset;
}
}
isObservingContentOffsetChange = NO;
return;
}
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
This can probably be made prettier, but it gets the idea across.
Use the contentOffset
property directly instead of setContentOffset:animated
to avoid the lag. In your example, change lines 73 and 74 to
self.tableViewMiddle.contentOffset = scrollView.contentOffset;
self.tableViewBottom.contentOffset = scrollView.contentOffset;
Old question, but here's an easier way.
Mark yourself as UITableViewDelegate:
class MultiTableViewController: UIViewController, UITableViewDelegate { ... }
Set yourself as delegate to the table views:
override func viewDidLoad() {
super.viewDidLoad()
leftTableView.delegate = self
rightTableView.delegate = self
}
(you can also do this step in interface builder).
Implement this method:
func scrollViewDidScroll(scrollView: UIScrollView) {
leftTableView.contentOffset = scrollView.contentOffset
rightTableView.contentOffset = scrollView.contentOffset
}
Done.
A table view has a scroll view, and the UITableViewDelegate protocol contains all the UIScrollViewDelegate methods, so you can just use them.