How to get notified when scrollToRowAtIndexPath finishes animating

前端 未结 6 1859
梦如初夏
梦如初夏 2020-11-30 09:57

This is a follow-up to How to get notified when a tableViewController finishes animating the push onto a nav stack.

In a tableView I want to deselect a

相关标签:
6条回答
  • 2020-11-30 09:58

    You can use the table view delegate's scrollViewDidEndScrollingAnimation: method. This is because a UITableView is a subclass of UIScrollView and UITableViewDelegate conforms to UIScrollViewDelegate. In other words, a table view is a scroll view, and a table view delegate is also a scroll view delegate.

    So, create a scrollViewDidEndScrollingAnimation: method in your table view delegate and deselect the cell in that method. See the reference documentation for UIScrollViewDelegate for information on the scrollViewDidEndScrollingAnimation: method.

    0 讨论(0)
  • 2020-11-30 10:02

    You can include the scrollToRowAtIndexPath: inside a [UIView animateWithDuration:...] block which will trigger the completion block after all included animations conclude. So, something like this:

    [UIView
        animateWithDuration:0.3f
        delay:0.0f
        options:UIViewAnimationOptionAllowUserInteraction
        animations:^
        {
            // Scroll to row with animation
            [self.tableView scrollToRowAtIndexPath:indexPath
                                atScrollPosition:UITableViewScrollPositionTop
                                        animated:YES];
        }
        completion:^(BOOL finished)
        {
            // Deselect row
            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
        }];
    
    0 讨论(0)
  • 2020-11-30 10:06

    Swift 5

    The scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) delegate method is indeed the best way to execute a completion on a scroll-to-row animation but there are two things worth noting:

    First, the documentation incorrectly says that this method is only called in response to setContentOffset and scrollRectToVisible; it's also called in response to scrollToRow (https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619379-scrollviewdidendscrollinganimati).

    Second, despite the fact that the method is called on the main thread, if you're running a subsequent animation here (one after the scroll has finished), it will still hitch (this may or may not be a bug in UIKit). Therefore, simply dispatch any follow-up animations back onto the main queue which just ensures that the animations will begin after the end of the current main task (which appears to include the scroll-to-row animation). Doing this will give you the appearance of a true completion.

    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        DispatchQueue.main.async {
            // execute subsequent animation here
        }
    }
    
    0 讨论(0)
  • 2020-11-30 10:07

    Implementing this in a Swift extension.

    //strong ref required
    private var lastDelegate : UITableViewScrollCompletionDelegate! = nil
    
    private class UITableViewScrollCompletionDelegate : NSObject, UITableViewDelegate {
        let completion: () -> ()
        let oldDelegate : UITableViewDelegate?
        let targetOffset: CGPoint
        @objc private func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
            scrollView.delegate = oldDelegate
            completion()
            lastDelegate = nil
        }
    
        init(completion: () -> (), oldDelegate: UITableViewDelegate?, targetOffset: CGPoint) {
            self.completion = completion
            self.oldDelegate = oldDelegate
            self.targetOffset = targetOffset
            super.init()
            lastDelegate = self
        }
    }
    
    extension UITableView {
        func scrollToRowAtIndexPath(indexPath: NSIndexPath, atScrollPosition scrollPosition: UITableViewScrollPosition, animated: Bool, completion: () -> ()) {
            assert(lastDelegate == nil, "You're already scrolling.  Wait for the last completion before doing another one.")
            let originalOffset = self.contentOffset
            self.scrollToRowAtIndexPath(indexPath, atScrollPosition: scrollPosition, animated: false)
    
            if originalOffset.y == self.contentOffset.y { //already at the right position
                completion()
                return
            }
            else {
                let targetOffset = self.contentOffset
                self.setContentOffset(originalOffset, animated: false)
                self.delegate = UITableViewScrollCompletionDelegate(completion: completion, oldDelegate: self.delegate, targetOffset:targetOffset)
                self.scrollToRowAtIndexPath(indexPath, atScrollPosition: scrollPosition, animated: true)
            }
        }
    }
    

    This works for most cases although the TableView delegate is changed during the scroll, which may be undesired in some cases.

    0 讨论(0)
  • 2020-11-30 10:18

    To address Ben Packard's comment on the accepted answer, you can do this. Test if the tableView can scroll to the new position. If not, execute your method immediately. If it can scroll, wait until the scrolling is finished to execute your method.

    - (void)someMethod
    {
        CGFloat originalOffset = self.tableView.contentOffset.y;
        [self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
        CGFloat offset = self.tableView.contentOffset.y;
    
        if (originalOffset == offset)
        {
            // scroll animation not required because it's already scrolled exactly there
            [self doThingAfterAnimation];
        }
        else
        {
            // We know it will scroll to a new position
            // Return to originalOffset. animated:NO is important
            [self.tableView setContentOffset:CGPointMake(0, originalOffset) animated:NO];
            // Do the scroll with animation so `scrollViewDidEndScrollingAnimation:` will execute
            [self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
        }
    }
    
    - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
    {
        [self doThingAfterAnimation];
    }
    
    0 讨论(0)
  • 2020-11-30 10:20

    try this

    [UIView animateWithDuration:0.3 animations:^{
        [yourTableView scrollToRowAtIndexPath:indexPath 
                             atScrollPosition:UITableViewScrollPositionTop 
                                     animated:NO];
    } completion:^(BOOL finished){
        //do something
    }];
    

    Don't forget to set animated to NO, the animation of scrollToRow will be overridden by UIView animateWithDuration.

    Hope this help !

    0 讨论(0)
提交回复
热议问题