在动画移动对象(UIView)上未检测到点击手势

Posted

技术标签:

【中文标题】在动画移动对象(UIView)上未检测到点击手势【英文标题】:Tap gesture not detect on animating-moving object(UIView) 【发布时间】:2019-01-23 10:11:06 【问题描述】:

我尝试使用动画简单地移动UIView。这是我的示例代码。

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tap1(gesture:)))
    tapGesture.numberOfTapsRequired = 1
    tapGesture.numberOfTouchesRequired = 1
    self.tempView.addGestureRecognizer(tapGesture)
    
    self.tempView.frame = CGRect(x: self.tempView.frame.origin.x, y: 0, width: self.tempView.frame.width, height: self.tempView.frame.height)
    
    UIView.animate(withDuration: 15, delay: 0.0, options: [.allowUserInteraction, .allowAnimatedContent], animations: 
      self.tempView.frame = CGRect(x: self.tempView.frame.origin.x, y: UIScreen.main.bounds.height - 100, width: self.tempView.frame.width, height: self.tempView.frame.height)
  

    )  (_) in
      
    

这里我提到点击手势方法:

@objc func tap1(gesture: UITapGestureRecognizer) 
    print("tap with object")

问题是,当UIView 正在制作动画并从一个位置移动到另一个位置时,此时点击手势不起作用。

【问题讨论】:

您是否检查过userInteractionEnabled 已为self.tempView 启用? 【参考方案1】:

虽然视觉视图改变了位置,但它并没有这样做。如果您在动画内部检查它的帧,则该帧将始终是您的结束帧。因此,您可以在其结束位置的任何位置点击它。 (这也假设您在制作动画时启用了用户交互)。

您的案例需要的是使用计时器手动移动视图。这需要更多的努力,但很容易做到。计时器应该以一些不错的 FPS (例如 60)触发,并且每次触发帧时都应该更新到其插值位置。

要做插值,你可以简单地通过组件来做:

func interpolateRect(from: CGRect, to: CGRect, scale: CGFloat) -> CGRect 
    return CGRect(x: from.minX + (to.minX - from.minX) * scale, y: from.minY + (to.minY - from.minY) * scale, width: from.width + (to.width - from.width) * scale, height: from.height + (to.height - from.height) * scale)

在大多数情况下,比例自然必须介于 0 和 1 之间。

现在您需要一个方法来使用计时器设置动画,例如:

func animateFrame(to frame: CGRect, animationDuration duration: TimeInterval) 
    self.animationStartFrame = tempView.frame // Assign new values
    self.animationEndFrame = frame // Assign new values
    self.animationStartTime = Date() // Assign new values

    self.currentAnimationTimer.invalidate() // Remove current timer if any

    self.currentAnimationTimer = Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true)  timer in
        let timeElapsed = Date().timeIntervalSince(self.animationStartTime)
        let scale = timeElapsed/duration
        self.tempView.frame = self.interpolateRect(from: self.animationStartFrame, to: self.animationEndFrame, scale: CGFloat(max(0.0, min(scale, 1.0))))
        if(scale >= 1.0)  // Animation ended. Remove timer
            timer.invalidate()
            self.currentAnimationTimer = nil
        
     
 

公平地说,这段代码仍然可以减少,因为我们使用的是带有块的计时器:

func animateFrame(to frame: CGRect, animationDuration duration: TimeInterval) 
    let startFrame = tempView.frame
    let endFrame = frame
    let animationStartTime = Date()

    self.currentAnimationTimer.invalidate() // Remove current timer if any

    self.currentAnimationTimer = Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true)  timer in
        let timeElapsed = Date().timeIntervalSince(animationStartTime)
        let scale = timeElapsed/duration
        self.tempView.frame = self.interpolateRect(from: startFrame, to: endFrame, scale: CGFloat(max(0.0, min(scale, 1.0))))
        if(scale >= 1.0)  // Animation ended. Remove timer
            timer.invalidate()
            if(timer === self.currentAnimationTimer) 
                self.currentAnimationTimer = nil // Remove reference only if this is the current timer
            
        
     
 

这样我们就不需要在我们的类中存储这么多的值,而是将所有的值保留在方法中。

提到如何进行缓动可能会很有趣。诀窍只是操纵我们的scale。考虑这样的事情:

func easeInScaleFromLinearScale(_ scale: CGFloat, factor: CGFloat = 0.2) -> CGFloat 
    return pow(scale, 1.0+factor)

func easeOutScaleFromLinearScale(_ scale: CGFloat, factor: CGFloat = 0.2) -> CGFloat 
    return pow(scale, 1.0/(1.0+factor))

现在我们需要做的就是在插值时使用它:

self.tempView.frame = self.interpolateRect(from: startFrame, to: endFrame, scale: easeInScaleFromLinearScale(CGFloat(max(0.0, min(scale, 1.0)))))

修改因子会改变效果。系数越高,效果越高。而使用零的因子意味着一条线性曲线。

有了它,您几乎可以制作任何类型的动画。唯一的规则是你的函数必须遵循f(0) = 0f(1) = 1。这意味着它从起始位置开始,在结束位置结束。

虽然有些曲线有点棘手。 EaseInOut 可能看起来很简单,但事实并非如此。我们可能希望在[-PI/2, PI/2] 范围内实现类似sin 的东西。然后用线性函数平衡这个函数。这是我发现的实现之一:

return (sin((inputScale-0.5) * .pi)+1.0)/2.0 * (magnitude) + (inputScale * (1.0-magnitude))

如果您使用一些“在线图形计算器”来查找您的方程式,然后将结果转换为您的函数,这会有所帮助。

【讨论】:

以上是关于在动画移动对象(UIView)上未检测到点击手势的主要内容,如果未能解决你的问题,请参考以下文章

状态栏区域未检测到点击手势?

如何检测手指移入或移出我的自定义 UIView

带有子 UIView(保存内容)的 UIScrollView 未检测到点击

翻译动画在第二次点击之前被忽略

与画外音直接互动?

ImageButton 似乎没有检测到点击(Scene2d.ui)