动画移回原始位置后未调用 UIView 动画

UIView Animation not being called after animation moves back to original location

目前我正在使用一个按钮来激活一个 UIView 动画,在它的完成块中我正在使用一个 CABasicAnimation return 它到它的起始位置。动画首先运行顺利,returns 到其原始起点,但是当我再次按下按钮时,动画自动从原始 UIView 动画结束的位置开始,而它应该从原始起点开始。

class ObjectCreateMoveViewController: UIViewController, CAAnimationDelegate {

  let square = UIView()

  override func viewDidLoad() {
    super.viewDidLoad()

    createSquare()

  }

  func createSquare() {

    square.frame =  CGRect(x: 10, y: 10, width: 100, height: 100)
    square.backgroundColor = UIColor.red
    square.layer.borderColor = UIColor.blue.cgColor
    square.layer.borderWidth = 4
    square.layer.cornerRadius = 2
    view.addSubview(square)
  }


  func transformAnimation () {

    let transAnimation = CABasicAnimation(keyPath: "transform.translation")
    transAnimation.toValue = NSValue(cgPoint: CGPoint(x: 10, y: 10))

    let rotAnimation = CABasicAnimation(keyPath: "transform.rotation")
    rotAnimation.toValue = -CGFloat.pi

    let scaleAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
    scaleAnimation.toValue = NSNumber(value: 1)

    let groupTransform = CAAnimationGroup()
    groupTransform.duration = 3
    groupTransform.delegate = self
    groupTransform.beginTime = CACurrentMediaTime()
    groupTransform.animations = [transAnimation, rotAnimation, scaleAnimation]
    groupTransform.isRemovedOnCompletion = false
    groupTransform.fillMode = kCAFillModeForwards
    groupTransform.setValue("circle", forKey: "transform")
    square.layer.add(groupTransform, forKey: "transform")
  }

  func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    if flag {
      //should be (10,10)but when I printed out the ending positions it was (60,60)
      square.layer.position.x = 60
      square.layer.position.y = 60
      print("This is the x point \(square.layer.position.x)")
      print("This is the y point \(square.layer.position.y)")
    }
  }

  @IBAction func transformButtonPressed(_ sender: Any) {

    UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut, animations: {

      let translateTransform = CGAffineTransform(translationX: 150, y: 200)
      self.square.transform = translateTransform.scaledBy(x: 1.5, y: 1.5).rotated(by: CGFloat.pi/2)

    }, completion: { _ in

      self.transformAnimation()

    })
  }
}

这里发生了一些事情,所以让我先给你一些背景知识,然后再看看发生了什么,看看哪里出了问题。

剧透:它是isRemovedOnCompletion = false和前锋fillMode的组合。


一些背景

在 iOS 上,每个视图总是有一个它拥有和管理的图层。像视图的 frametransform 一样设置 属性 会自动设置图层的 frametransform。对于图层,设置的这个值有时称为 "model value" 以区别于 "presentation value","presentation value" 是图层在正在进行的动画期间在屏幕上的显示方式。实际上这意味着两件事:

  1. 如果在视图使用 UIView 动画进行动画处理时尝试检查图层位置的值,则 position 值将成为最终值,该值在动画块。要获得屏幕上显示的 "current" 值,必须查看图层的表示层的值。

  2. 如果向图层添加显式 CAAnimation,该图层将在屏幕上对变化进行动画处理,但如果动画 属性 在动画期间被检查,它将保持不变.一旦动画完成并被移除,该层将再次渲染它的模型值,使其看起来好像跳回到旧值,除非该值也被更改。

这种情况下发生了什么

开始

视图(及其层)以 (10, 10) 的帧原点和 (100, 100) 的大小使其成为 center(层的 position(60, 60)。由于未设置转换,因此使用 "identity transform"(无转换)。

触发动画

按下按钮时,将执行动画块,将视图(及其层)的变换更改为平移 (150, 200) 比例 (1.5, 1.5)π/2 的旋转。这会更改图层的 transform "model" 值。

运行完成块

第一个动画完成后,触发第二个动画。此时模型值仍然是它们在动画块中设置的值。

三个变换动画配置为平移 (10, 10)、旋转 和缩放 1。这些动画组被配置为 不会 在完成后被删除。此时模型值更新。

动画组完成

动画组完成但未删除。此时模型值仍然是在原始动画块中设置的变换。不删除 CAAnimation 在模型(属性 是什么)和演示文稿(它在屏幕上的显示方式)之间存在差异。

请注意,此时 position ,因为它从未更改过。更改 transform 属性 不会 更改 position 属性,仅更改图层出现在屏幕上的位置。

再次触发第一个动画

下次按下按钮时,将再次触发第一个动画。它将从 "old" 值到 "new" 值进行动画处理。由于 transform 属性(模型的)从未更改过,因此它们是相同的。

我不记得它是否被记录在案,但 UIView 动画使用 属性 在向图层添加动画(发生在幕后)时作为关键更改。

因为已将第二个动画添加到键 "transform" 的层中,并且层中每个唯一键只能有一个动画。组动画被移除。这使得视图看起来像跳回到第一个动画的 "end value"。

据我记得 UIKit 在做什么,然后视图会执行一个 3 秒的动画,从一个值到同一个值(即不改变任何东西)。

一旦 UIView 动画完成,完成块再次运行第二个动画,将组动画添加到图层。

如何修复

真正的问题是这两行:

groupTransform.isRemovedOnCompletion = false
groupTransform.fillMode = kCAFillModeForwards

他们一起看起来像是在做正确的事情,因为它在屏幕上看起来是正确的,但正如您在上面看到的那样,他们在动画完成很长时间后仍然存在模型和表示层之间的暂时差异。

作为一个很好的经验法则:删除动画的"only"时间是如果它正在动画化view/layer的消失并且view/layer 完成后将被删除。


一种解决方法是删除这两行,然后在将动画组添加到层.

但是,在这种情况下,可以使用完成块中的另一个 UIView 动画来实现相同的动画。或者,您可以使用具有两个 3 秒步骤的 UIView 关键帧动画。

尽可能坚持使用 UIView 动画有利于更新模型值。

David Rönnqvist 给了你一个非常好的工作描述,但是如果你想要一个非常非常简单的选项 "animating it back the way it came" ...

选项 1 - 使用 .autoReverse:

@IBAction func transformButtonPressed(_ sender: Any) {

    UIView.animate(withDuration: 3, delay: 0, options: [.curveEaseInOut, .autoreverse], animations: {

        let translateTransform = CGAffineTransform(translationX: 150, y: 200)
        self.square.transform = translateTransform.scaledBy(x: 1.5, y: 1.5).rotated(by: CGFloat.pi/2)

    }, completion: { _ in

        self.square.transform = .identity

    })
}

选项 2 - 如果您想 "send it back" 执行另一项操作,例如:

@IBAction func transformButtonPressed(_ sender: Any) {

    UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut, animations: {

        let translateTransform = CGAffineTransform(translationX: 150, y: 200)
        self.square.transform = translateTransform.scaledBy(x: 1.5, y: 1.5).rotated(by: CGFloat.pi/2)

    }, completion: { _ in

        // un-comment to animate it back right away,
        // or leave commented, and call transformBack() from somewhere else
        //self.transformBack()

    })
}

func transformBack() -> Void {

    UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut, animations: {

        // resets transform to "none"
        self.square.transform = .identity

    }, completion: { _ in

    })

}