【问题标题】:How to get notified when scrollToRowAtIndexPath finishes animatingscrollToRowAtIndexPath 完成动画时如何获得通知
【发布时间】:2010-12-04 22:49:05
【问题描述】:

这是How to get notified when a tableViewController finishes animating the push onto a nav stack.的后续行动

tableView 中,我想取消选择带有动画的行,但只有在tableView 完成 动画滚动到所选行之后。发生这种情况时如何通知我,或者在完成的那一刻调用什么方法。

这是事情的顺序:

  1. 推送视图控制器
  2. viewWillAppear 我选择了某一行。
  3. viewDidAppear I scrollToRowAtIndexPath(到选定的行)。
  4. 然后当滚动结束时我想deselectRowAtIndexPath: animated:YES

这样,用户会知道他们为什么滚动到那里,但我可以淡出选择。
第4步是我还没有弄清楚的部分。如果我在 viewDidAppear 中调用它,那么当 tableView 滚动到那里时,该行已经被取消选择,这不好。

【问题讨论】:

    标签: cocoa-touch ios


    【解决方案1】:

    您可以使用表视图委托的scrollViewDidEndScrollingAnimation: 方法。这是因为UITableViewUIScrollView 的子类,而UITableViewDelegate 符合UIScrollViewDelegate。换句话说,一个table view就是一个scroll view,一个table view delegate也是一个scroll view delegate。

    因此,在您的表格视图委托中创建一个scrollViewDidEndScrollingAnimation: 方法并取消选择该方法中的单元格。有关scrollViewDidEndScrollingAnimation: 方法的信息,请参阅UIScrollViewDelegate 的参考文档。

    【讨论】:

    • 如果 iOS 提供一个带有完成块的版本(如UIView animateWithDuration:animations:completion)会很好,所以通知可以是特定于上下文的......
    • 如果表格视图决定不需要滚动动画怎么办(因为该行已经在屏幕上)?如果滚动动画方法没有触发,则永远不会取消选择该行。
    • @BenPackard,你可以提前检查一下...[myTableView.indexPathsForVisibleRows containsObject:myIndexPath]
    • 该方法在iOS 7.1上不调用不调用
    【解决方案2】:

    试试这个

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

    不要忘记将animation设置为NO,scrollToRow的动画会被UIView animateWithDuration覆盖。

    希望有帮助!

    【讨论】:

    • 我能够根据这个建议做我想做的事,所以我想我拥有你一个 :)
    • 使用此解决方案的注意事项:我使用此方法有一段时间了,直到我在滚动到的单元格中看到奇怪的行为。隐藏的图像会显示出来……无法解释。查看调试会显示它们不可见。但它们是在设备上运行时。我将问题隔离到scrollToRowAtIndexPath,动画块内的动画设置为NO。将它移到动画块之外,没有问题。但是没有动画......所以这种技术可能会导致问题。我更新为使用 James Huddleston 的 scrollViewDidEndScrollingAnimation 解决方案来解决问题。
    • 在 iOS13 中,执行此操作后我看到 cpu 使用率超过 100%。和动画都变慢了。
    【解决方案3】:

    要解决 Ben Packard 对已接受答案的评论,您可以这样做。测试 tableView 是否可以滚动到新位置。如果没有,请立即执行您的方法。如果它可以滚动,请等到滚动完成后再执行您的方法。

    - (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];
    }
    

    【讨论】:

    • 使用[self.tableView.indexPathsForVisibleRows containsObject:path]怎么样?
    • 确实,如果 indexPath 可见,它肯定需要滚动才能正确定位,所以在这种情况下你可以跳过contentOffset 摆弄。但是,如果 indexPath 可见,tableView 可能会也可能无法将其滚动到位,因此您应该检查 contentOffsets(与上图相同)。
    【解决方案4】:

    您可以将scrollToRowAtIndexPath: 包含在[UIView animateWithDuration:...] 块中,这将在所有包含的动画结束后触发完成块。所以,是这样的:

    [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];
        }];
    

    【讨论】:

    • 聪明 :) 虽然这会覆盖 scrollToRowAtIndexPath 的默认动画持续时间?
    • 正确,它确实会覆盖默认持续时间,但根据您设置的值,它可以使您的应用程序明显/独特地运行(这可能是一件好事),或者您可以找到一个如此接近的值默认情况下,您的用户几乎听不见。
    • 我发现这个解决方案不幸地在滚动长列表时会导致一些伪影:除了滚动停止时可见的单元格之外的所有单元格都没有绘制并且显示为白色,从而导致闪烁。
    • 对自己(me)的弱引用也是不合理的。
    • 从 iOS 9 开始,如果您使用 0.00 的动画 durationNOanimated 动画,滚动长列表似乎不会“闪烁”。
    【解决方案5】:

    斯威夫特 5

    scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) 委托方法确实是在滚动到行动画上执行完成的最佳方式,但有两点值得注意:

    首先,文档错误地指出该方法仅在响应setContentOffsetscrollRectToVisible 时被调用;它也被调用以响应scrollToRow (https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619379-scrollviewdidendscrollinganimati)。

    其次,尽管该方法是在主线程上调用的,但如果您在此处运行后续动画(滚动完成后的动画),它仍然会出现问题(这可能是也可能不是UIKit)。因此,只需将任何后续动画分派回主队列,这只是确保动画将在当前主任务结束后开始(这似乎包括滚动到行动画)。这样做会给你一个真正完成的外观。

    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        DispatchQueue.main.async {
            // execute subsequent animation here
        }
    }
    

    【讨论】:

    • 还有一个问题,如果该行已经在该位置,则此方法不会触发。仅供参考。
    【解决方案6】:

    在 Swift 扩展中实现这一点。

    //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)
            }
        }
    }
    

    这适用于大多数情况,尽管在滚动期间更改了 TableView 委托,这在某些情况下可能是不希望的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多