调用 touchesBegan 时动画 UIView 对象跳转

Animated UIView object jumps when touchesBegan is invoked

我有一个 UILabel 对象,它正在动画化,上下移动。我在 Xcode 8.3 中使用 Swift 3 编写代码。用户可以平移此对象并拖动到一个位置以获得一些分数。我正在使用 touchesXXX 手势处理 pan/drag。然而,当我点击并立即放开它时,这个对象出于某种原因垂直跳到它的位置上方,我无法弄清楚为什么现在一个星期了...

当我启用一些调试时,我只能看到 touchesBegan 被调用(touchesMoved 没有按预期调用,touchesEnded 也没有,这对我来说是意外的)。如果我手动禁用对象上的动画,它会按预期工作并且可以毫不费力地平移对象并且显然没有对象跳跃。

这里是相关代码的摘录:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first
    let touchLocation = touch!.location(in: self.view)

    if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
        //pauseLayer(self.optOneLbl.layer)      <<<<< comment #1
        //optOneLbl.translatesAutoresizingMaskIntoConstraints = true      <<<<< comment #2
        optOneLbl.center = touchLocation
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first
    let touchLocation = touch!.location(in: self.view)
    var sender: UILabel

    if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
        self.optOneLbl.center = touchLocation
        sender = self.optOneLbl
    }

    // identify which letter was overlapped
    var overlappedView : UILabel
    if (sender.frame.contains(letter1.center)) {
        ...
    }
    else {
        return
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)
}

阅读其他 SO 问题的回复后,我认为按照 touchesBegan 中的上述评论 #1 以编程方式禁用标签动画可能会有所帮助,但问题仍然存在。另外,我认为可能是自动布局导致了这种奇怪的跳跃。因此,我根据评论 #2 启用了 translatesAutoresizingMaskIntoConstraints,但它也没有帮助。

谁能看出我哪里处理不当? 感谢阅读!

编辑:

应@agibson007 的要求,我添加了动画代码摘录以供参考:

UIView.animate(withDuration: 12.0, delay: 0.0, options: [ .allowUserInteraction, .curveLinear, .autoreverse, .repeat ], animations: {
    self.optOneLbl.center = CGPoint(x: self.optOneLbl.center.x, y: screenSize.midY*1.1)
})

您可以使用 UIPanGestureRecognizer 实现同样的效果。将平移手势添加到您的标签并将平移上的标签移动为

@IBAction func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
    if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {

        let translation = gestureRecognizer.translation(in: self.view)
        yourLabel!.center = CGPoint(x: yourLabel!.center.x + translation.x, y: yourLabel!.center.y + translation.y)
        gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
    }
}

您可以从情节提要或以编程方式在 viewDidLoad 中将 gesturerecognizer 添加到您的 UILabel

let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
yourLabel.addGestureRecognizer(gestureRecognizer)

使用 Pan 手势作为优势,您不必跟进 touchesXX 方法。至于为什么您的观点正在上升,我认为您可能也在其他地方重置了标签框架。手势也有可能与触摸发生冲突。由于这两种情况在您的代码中都不明显,我们需要更多代码来确认。

更改标签位置后需要remove/reset动画。动画不知道您更新了值,并试图从一开始就保持在相同的 byValue 范围内。这是更新动画的工作示例。

import UIKit

class ViewController: UIViewController {

    var optOneLbl = UILabel()


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.


        optOneLbl = UILabel(frame: CGRect(x: 20, y: 50, width: self.view.bounds.width - 40, height: 40))
        optOneLbl.textAlignment = .center
        optOneLbl.text = "I Will Be Moving Up and Down"
        optOneLbl.textColor = .white
        optOneLbl.backgroundColor = .blue
        self.view.addSubview(optOneLbl)

        //starts it in the beginnning with a y
        fireAnimation(toY:  self.view.bounds.midY*1.1)


    }

    func fireAnimation(toY:CGFloat) {
        UIView.animate(withDuration: 12.0, delay: 0.0, options: [ .allowUserInteraction, .curveLinear, .autoreverse, .repeat ], animations: {
            self.optOneLbl.center = CGPoint(x: self.optOneLbl.center.x, y:toY)
        })
    }



    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self.view)

        if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
            //re
            optOneLbl.layer.removeAllAnimations()
            optOneLbl.center = touchLocation
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self.view)
        var sender: UILabel

        if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
            self.optOneLbl.center = touchLocation
            sender = self.optOneLbl
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        //restart animation after finished and change the Y if you want.  
        // you could change duration or whatever
        fireAnimation(toY: self.view.bounds.height - optOneLbl.bounds.height)
    }

}