Syncing the scroll position of multiple UITableVie

2020-06-06 07:24发布

问题:

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?

回答1:

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.



回答2:

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;


回答3:

Old question, but here's an easier way.

  1. Mark yourself as UITableViewDelegate:

    class MultiTableViewController: UIViewController, UITableViewDelegate { ... }
    
  2. 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).

  3. 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.