简体   繁体   English

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

[英]Tap gesture not detect on animating-moving object(UIView)

I try to simply move the UIView using animation.我尝试使用动画简单地移动UIView Here is my sample code.这是我的示例代码。

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
      
    }

Here I mention tap gesture method:这里我提到点击手势方法:

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

The problem is, when UIView is animating and moves from one position to another position, at that time tap gesture is not working.问题是,当UIView动画并从一个位置移动到另一个位置时,此时点击手势不起作用。

Although visually view changes position it does not do so.尽管视觉上的视图会改变位置,但它不会这样做。 If you check it's frame while inside animation the frame will always be whatever is your end frame.如果您在动画内部检查它的帧,则该帧将始终是您的结束帧。 So you can tap it wherever its end position will be.因此,您可以在其结束位置所在的任何位置点击它。 (This also assumes that you have enabled user interaction while animating which you did). (这也假设您在制作动画时启用了用户交互)。

What you need for your case is to manually move your view with timer.您的案例需要的是使用计时器手动移动您的视图。 It takes a bit more effort but can be easily doable.这需要更多的努力,但很容易做到。 A timer should trigger with some nice FPS like 60 and each time it fires a frame should be updated to its interpolated position.计时器应该以一些不错的 FPS 触发,例如 60,并且每次触发帧时都应该将其更新到其插值位置。

To do interpolation you can simply do it by component:要进行插值,您可以简单地按组件进行:

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

Scale naturally has to be between 0 and 1 for most cases.在大多数情况下,比例自然必须介于 0 和 1 之间。

Now you need to have a method to animate with timer like:现在您需要有一种方法来使用计时器进行动画处理,例如:

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

To be fair this code can still be reduced since we are using timer with block:公平地说,由于我们使用带块的计时器,因此仍然可以减少此代码:

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

This way we don't need to store so many values in our class but keep the all inside the method.这样我们就不需要在我们的类中存储这么多值,而是将 all 保留在方法中。

It may be interesting to mention how to do easing.提及如何进行缓动可能会很有趣。 The trick is only to manipulate our scale .诀窍只是操纵我们的scale Consider something like this:考虑这样的事情:

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

Now all we need to do is use it when interpolating:现在我们需要做的就是在插值时使用它:

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

Modifying the factor will change the effect.修改因子会改变效果。 Higher the factor, higher effect.系数越高,效果越好。 And using a factor at zero would mean a linear curve.使用零系数意味着线性曲线。

With this you can do pretty much any type of animation.有了这个,你几乎可以做任何类型的动画。 The only rule is that your function must follow f(0) = 0 and f(1) = 1 .唯一的规则是您的函数必须遵循f(0) = 0f(1) = 1 This means it starts at starting position and end at ending position.这意味着它从起始位置开始并在结束位置结束。

Some curves are a bit more tricky though.不过,有些曲线有点棘手。 EaseInOut might seem simple but it is not. EaseInOut可能看起来很简单,但事实并非如此。 We would probably want to implement something like sin in range [-PI/2, PI/2] .我们可能想要在[-PI/2, PI/2]范围内实现类似sin东西。 Then balance this function with linear function.然后用线性函数平衡这个函数。 This is one of my implementations I found:这是我发现的实现之一:

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

It helps if you play around with some "online graphing calculator" to find your equations and then convert results into your functions.如果您使用一些“在线图形计算器”来查找方程,然后将结果转换为函数,这会有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM