在scrollViewWillEndDragging中将targetContentOffset更新为错误方向的值不会产生动画效果。

7
我正在scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)中设置targetContentOffset的新值,以创建UITableView中的自定义分页解决方案。 当为targetContentOffset设置与速度指向相同滚动方向的坐标时,它按预期工作。
然而,当反向“后退”快速拖动时,它会立即“回弹”,没有任何动画效果,这看起来很糟糕。 有什么想法可以解决这个问题吗?
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    // a lot of caluculations to detemine where to "snap scroll" to
    if shouldSnapScrollUpFromSpeed || shouldSnapScrollUpFromDistance {
        targetContentOffset.pointee.y = -scrollView.frame.height
    } else if shouldSnapScrollDownFromSpeed || shouldSnapScrollDownFromDistance {
        targetContentOffset.pointee.y = -detailViewHeaderHeight
    }
}

我可以尝试计算出这个“bug”出现的时间,并可能使用另一种“快速滚动”的方法。有没有关于如何使用targetContentOffset.pointee.y来解决问题的建议?


1
你的问题标题真的帮了我大忙。非常感谢你。我一直无法确定动画在哪些条件下会中断。在我的情况下,我将通过始终自动向“右”方向滚动来修复它。再次感谢! - Artem Stepanenko
3个回答

1

我找到了一个还不错的解决方案(或变通方法)。如果有更好的解决方案,请告诉我。

我刚刚检测到了不想要的“非动画捕捉滚动”出现的时机,然后将targetContentOffset.pointee.y的新值设置为当前偏移值(停止它),并使用scrollView的setContentOffset(_:animated:)方法来设置所需的偏移目标值。

if willSnapScrollBackWithoutAnimation {
    targetContentOffset.pointee.y = -scrollView.frame.height+yOffset //Stop the scrolling
    shouldSetTargetYOffsetDirectly = false
}

if let newTargetYOffset = newTargetYOffset {
    if shouldSetTargetYOffsetDirectly {
        targetContentOffset.pointee.y = newTargetYOffset
    } else {
        var newContentOffset = scrollView.contentOffset
        newContentOffset.y = newTargetYOffset
        scrollView.setContentOffset(newContentOffset, animated: true)
    }
}

你是如何检测 willSnapScrollBackWithoutAnimation 的? - Natan R.
当滚动在可捕捉间隔(滚动的y偏移量)内且速度低于所需值(使用1.0),并且速度不为0时,将willSnapBackWithoutAnimation设置为true。 - Sunkas
@Sunkas为什么不直接发布willSnapScrollBackWithoutAnimation的实际代码,而要解释它呢? - strangetimes
好问题。本应该这样做的。不幸的是,我现在无法访问代码了。 - Sunkas
1
@Sunkas 你好,虽然你在之前的回答中提出了一个很好的想法,但实际上比这更简单些。 - Fattie

1

在我看来,这个问题的唯一解决方案是"@sunkas solution"。它在概念上很简单,但需要注意的细节非常多。

func scrollViewWillEndDragging(_ sv: UIScrollView,
  withVelocity vel: CGPoint,
  targetContentOffset: UnsafeMutablePointer<CGPoint>) {

  print("Apple wants to end at \(targetContentOffset.pointee)")
  ...

假设UIKit希望将抛出结束在“355.69”。

  ...
  ... your calculations and logic
  solve = 280

你进行你自己的计算和逻辑。你希望投掷结果在280处结束。

从概念上讲,实际上很简单:

  ... your calculations and logic
  solve = 280
targetContentOffset.pointee.y = solve
UIView.animate(withDuration: secs, delay: 0, options: .curveEaseOut, animations: {
    theScroll.contentOffset.y = solve
})

就是这样。

但是要实现与UIKit的行为和感觉完全匹配的完美结果,有很多复杂的问题。

  • 找出你的“解决方案”并确定它的下一个位置是一项巨大的工作。

  • 你需要很多代码来判断用户是否真的将其扔到了下一个停靠位置,还是只是“稍微移动了一下手指,然后放开了”。

  • 如果他们只是“稍微移动了一下手指,然后放开了”,你必须决定你的下一个解决方案是他们开始的那个,还是“下一个”或“上一个”点击停靠点。

  • 一个困难的问题是,在现实中,你会想使用弹簧物理效果来进行动画处理,以便与UIKit的正常停止相匹配。

  • 更棘手的是,当你做弹簧动画时,你需要找出并匹配手指的扔出速度,否则看起来就不对了。

这真是一项艰巨的任务。


谢谢你的“无功而返”吧。 - bobby123uk
嘿-冠军,你想知道什么?我已经提供了完整的代码。关于其他细节(五个要点),很遗憾,这些问题是不可能有“一种解决方案”的。例如,在第一点中,它完全取决于你的屏幕以及它的工作方式。至于第4点,你可以在任何地方看到“弹簧物理例程代码”(只需向UIView.animate添加一个参数),但是每种情况都是完全不同的,因此我不能“给出代码”。如果你浏览我的答案页面,你会发现我提供了很多著名的代码解决方案:) 有什么具体问题需要我帮忙吗? - Fattie
忘记添加 @bobby123uk 标签。 - Fattie
就像OP所问的那样,如何以相反方向进行动画 @Fattie。 - bobby123uk
啊朋友,我提供的那个将会在双向上进行动画。 - Fattie

0
在以下代码中,threshold被视为移动内容与拖动方向相同的触发点。如果未达到此阈值,则内容将返回到其原始位置(相反方向)。
目标内容偏移量期望在其动量方向上具有一个值,因此您需要先停止动量。然后只需使用标准动画块移动视图即可。
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        
        let distance = distanceTravelled(scrollView)
                
        let isForwardDirection = (velocity.x > 0 || distance < 0)
        
        if threshold(velocity, distance) {
            let rect = makeRect(scrollView)
            if let attribute = attribute(for: rect, isForwardDirection) {
                currentIndexPath = attribute.indexPath
                targetContentOffset.pointee = point(for: attribute)
            }
        } else if let attribute = flowLayout.layoutAttributesForItem(at: currentIndexPath) {
            targetContentOffset.pointee = scrollView.contentOffset
            DispatchQueue.main.async {
                let point = self.point(for: attribute)
                UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseOut, .allowUserInteraction]) {
                    scrollView.contentOffset = point
                }
            }
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接