CollectionView 与 touchesBegan 和 touchesEnded 方法中的动画混淆

Posted

技术标签:

【中文标题】CollectionView 与 touchesBegan 和 touchesEnded 方法中的动画混淆【英文标题】:CollectionView messes with animations in touchesBegan and touchesEnded methods 【发布时间】:2018-03-29 22:34:27 【问题描述】:

我有一个集合视图,其中包含带有图像视图的单元格。当用户按下图像视图(使用 touchesBegan)时触发一个动画,当用户释放时触发另一个动画(使用 touchesEnded)。

只有当我按住点击然后松开(延迟点击)时,动画才能完美运行,但是当我快速点击时,动画会像持续时间设置为 0 一样跳跃。

我相信这个问题是因为集合视图是滚动视图的子类,并且 scrollViews “通过启动计时器临时拦截触摸事件,并在计时器触发之前查看触摸手指是否有任何移动。如果计时器在位置没有明显变化的情况下触发,滚动视图将跟踪事件发送到内容视图的触摸子视图。”

https://developer.apple.com/documentation/uikit/uiscrollview#//apple_ref/doc/uid/TP40006922

据我所知,如果点击比触摸计时器快,我认为来自集合视图的触摸拦截会导致动画出现问题。当我使用常规视图而不是集合视图作为超级视图进行测试时,动画效果很好,不需要延迟点击。

如果是这种情况,那么动画是如何触发快速点击的呢?此外,我如何能够在不使用延迟点击的情况下触发动画?

如果不是这样,那么这个问题的原因可能是什么?

这是我的动画和触摸代码:

func animateClickerAndBallPoint(newXpositionForClicker: CGFloat, newXpositionForBallPoint: CGFloat, ballPoint: UIImageView) 
    UIView.animate(withDuration: 0.1) 
        self.frame.origin.x = newXpositionForClicker
        ballPoint.frame.origin.x = newXpositionForBallPoint
    


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
    guard let ballPoint = self.ballPoint else return
    self.animateClickerAndBallPoint(newXpositionForClicker: 288, newXpositionForBallPoint: 11, ballPoint: ballPoint)


override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) 
    guard let ballPoint = self.ballPoint else return

    if isInWritingMode == true 
        animateClickerAndBallPoint(newXpositionForClicker: 306, newXpositionForBallPoint: 26,  ballPoint: ballPoint)
        isInWritingMode = false

     else 
        animateClickerAndBallPoint(newXpositionForClicker: 297, newXpositionForBallPoint: 17, ballPoint: ballPoint)
        isInWritingMode = true
    

【问题讨论】:

没有看到你的代码,所有可以做的就是推测。您能否包含一些代码来显示您的动画、touchesBegan 和任何其他相关代码? @Rob 感谢您的建议,但我不认为 didHighlight 方法是我的最佳选择,主要是因为与其内部的单元格相比,要触摸的 imageView 非常小。 【参考方案1】:

作为替代方案,您可以考虑挂接到按钮的beginTrackingendTracking,而不是自己做touchesBegantouchesEnded

例如,您可以子类化按钮并提供您想要的任何动画:

class AnimatedButton: UIButton 

    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool 
        UIView.animate(withDuration: 0.25) 
            self.transform = .init(scaleX: 0.8, y: 0.8)
        

        return true
    

    override func endTracking(_ touch: UITouch?, with event: UIEvent?) 
        UIView.animate(withDuration: 0.25) 
            self.transform = .identity
        
    


产生:

或者,如果您想根据单元格的部分本身来执行此操作,您可以挂钩到集合视图的“突出显示”机制,如https://***.com/a/45664054/1271826 所示。


顺便说一句,您假设问题是容器视图弄乱了触摸。你100%确定是这个问题吗?这可能是一个基本的动画问题。例如

例如,如果您在第一个动画仍在运行时开始第二个动画,它不会完成第一个动画,而是会立即从动画中间的任何位置开始第二个动画(在现代 ios 版本中,使用当前行驶的任何速度)并过渡到新目的地。

例如,这里有两个视图,我正在以完全相同的距离向下移动一秒钟,然后再向后移动一秒钟。但是对于右边的视图,我在第一个动画的 0.1 秒开始了第二个动画(即中断了第一个动画):

如您所见,因为第二个示例正在中断您的动画,所以看起来它只是快速恢复。

我认为在这种情况下不太可能,但如果您使用自动布局,则必须小心。如果您执行任何触发自动布局引擎重新应用其约束的操作(这实际上可能是任何 UI 操作,例如在一些不相关的标签中设置文本这样无害的操作),这将停止您正在进行的任何动画和会将视图捕捉回约束规定它们应该在的位置。

如果视图是使用自动布局布局的,您可能还需要考虑使用自动布局为它们设置动画。例如,与其调整frame(或其他),不如为您的约束创建IBOutlet,为该约束更改constant,然后对layoutIfNeeded的调用进行动画处理。

归根结底,您可能想看看是否可以验证问题是否真的与触摸事件有关,而不是一些不相关的动画问题。

很遗憾,您的示例不足以让我们诊断问题的根源。您可以考虑创建一个MVCE, a minimal, verifiable, and complete example of the problem。我建议创建一个没有集合视图的 MVCE,而另一个有,这样您就可以确认集合视图是否实际上是问题的根源。但是,归根结底,在我们能够重现您的问题之前,我们很难帮助您解决问题。

【讨论】:

只是想知道,但这与继承 UIImageView 和使用 touchesBegan/Ended 有什么不同吗?由于当触摸事件进入控件的边界时会调用 begin/endTracking,所以不会像 touchesBegan/Ended 那样被 CollectionView 拦截触摸事件吗?在什么时候按钮或视图需要长按才能正确动画? 我的两个解决方案(按钮跟踪和单元格突出显示)的共同主题是我使用 UIKit 提供的最高抽象级别。例如。苹果已经解决了处理单元格中按钮的触摸问题,并且按钮内置了图像视图,那么我为什么要重新发明***呢?我只潜入我绝对需要的接触(例如,我需要合并或预测接触的绘图应用程序)。我尽可能避免处理接触,因为我只是厌倦了你提到的各种麻烦。但如果你愿意,你可以尝试在UIImageView 中处理触摸。 @leedex - 因为我们看不到问题,所以我们很难诊断它,但我建议您可能要确认问题确实与集合视图中的触摸有关而不是一些简单的动画问题。请参阅我修改后的答案末尾的注释。 嘿,罗伯!感谢更新。非常感激。所以问题原来是在第一个动画结束(已解决)之前,触摸拦截和在同一视图上触发新动画的混合。我相当确定我有一个触摸拦截问题,因为我在禁用滚动时进行了测试,并且动画在我单击视图的瞬间触发(这是所需的行为),而当启用滚动时,单击和动画之间存在延迟。现在我只是在我需要的时候使用一个 jerry-rigged 的​​解决方案来禁用/启用滚动。 嘿@leedex 通常来说,像 Rob 这样的帖子点赞是一种很好的做法,因为他确实花费了宝贵的时间来编写它。这对你有帮助,对我来说,让我们心存感激;)【参考方案2】:

你试过delaysContentTouches = false吗?

https://developer.apple.com/documentation/uikit/uiscrollview/1619398-delayscontenttouches

告诉 scrollView/collectionView 不要延迟对单元格的触摸。当我开始点击它们时,它帮助我立即制作响应单元。也没有让我的滚动出错。

【讨论】:

【参考方案3】:

我在scrollviews 中玩动画时遇到了同样的问题。 我通过在动画中使用该参数来修复它:UIViewAnimationOptions.allowUserInteraction

所以你的动画块最终会是这样:

UIView.animate(withDuration: duration,
                                   delay: 0,
                                   usingSpringWithDamping: CGFloat(0.30),
                                   initialSpringVelocity: CGFloat(10.0),
                                   options: UIViewAnimationOptions.allowUserInteraction,
                                   animations: 

                                    applyWhatEverTransform()

                    ,completion: completion)

我有点猜测您的问题,因为我们没有您的代码。但是,当然,如果您使用不同类型的动画,这将无法正常工作 ;)

如果这没有帮助。为了帮助您调试,您可以尝试禁用集合视图的滚动,以查看该事件是否拦截了您的触摸事件。

【讨论】:

谢谢你,我已经禁用滚动来测试,看起来一旦禁用滚动,动画就会按预期执行!嗯,这很有趣......我想这意味着一旦禁用滚动,任何用于检测触摸的魔法滚动视图都会停止使用。 看起来你的动画以某种方式被取消了......你有没有尝试覆盖你的 touchCancel 函数? 是的。我试过覆盖 touchesCancelled。实际上,动画总是会触发,如果我点击太快,它就会像持续时间为 0 一样跳跃。在测试 touchesCancelled 时,只有当用户在触摸后拖动手指时才会调用它。

以上是关于CollectionView 与 touchesBegan 和 touchesEnded 方法中的动画混淆的主要内容,如果未能解决你的问题,请参考以下文章

与 UITapGestureRecognizer 一起使用时未调用 collectionView 的 didSelectItemAt

为啥我的 UICollectionViewController 不能与两个 CollectionView 一起运行?

CollectionView 与单元格重叠

NSFetchedResultsController 与 CollectionView 问题与索引/单元格更新和删除操作

iOS开发实战——CollectionView点击事件与键盘隐藏结合案例

ios 两个 TableView 之间的联动, TableView 与 CollectionView 之间的联动