【问题标题】:UIView Animation not being called after animation moves back to original location动画移回原始位置后未调用 UIView 动画
【发布时间】:2017-06-23 17:48:06
【问题描述】:

目前我正在使用一个按钮来激活 UIView 动画,并且在它的完成块中,我正在使用 CABasicAnimation 将其返回到它的起始位置。动画一开始运行平稳并返回到其原始起点,但是当我再次按下按钮时,动画会自动从原始 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()

    })
  }
}

【问题讨论】:

  • 首先,如果你print(square.layer.debugDescription)在对其执行任何动画/变换之前,你会看到图层位置是60,60——也就是说,它是中心点而不是左上角-角落。其次,你改造UIView,然后改造UIView的layer。因此,下次您点击按钮时,View 本质上仍处于其翻译的末尾,而不是 layer 的翻译末尾。您的(当前)目标是否只是让它恢复原状?
  • @DonMag 是的,我正在尝试将其动画化回原样。我正在测试它是否可以通过移动图层而不是视图来进行

标签: ios swift core-animation uiviewanimation cabasicanimation


【解决方案1】:

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

剧透:这是isRemovedOnCompletion = false 和转发fillMode 的组合。


一些背景

在 iOS 上,每个视图总是有一个它拥有和管理的层。设置视图的frametransform 之类的属性会自动设置图层的frametransform。对于图层,此设置的值有时称为“模型值”,以将其与“演示值”区分开来,“演示值”是图层在正在进行的动画期间出现在屏幕上的方式。在实践中,这意味着两件事:

  1. 如果在视图使用 UIView 动画制作动画时尝试检查图层位置的值,那么 position 值将成为动画块中分配的最终值。要获得屏幕上显示的“当前”值,必须查看层的表示层的值。

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

在这种情况下发生了什么

开始

视图(及其层)以(10, 10) 的帧原点和(100, 100) 的大小为星形,使其成为center(层的position(60, 60)。由于没有设置变换,所以使用“恒等变换”(无变换)。

触发动画

按下按钮时,将执行动画块,将视图(以及它的图层)的变换更改为平移(150, 200)、缩放(1.5, 1.5)和旋转π/2 .这会更改图层的transform“模型”值。

运行完成块

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

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

动画组完成

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

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

再次触发第一个动画

下次按下按钮时,会再次触发第一个动画。它将从“旧”值动画到“新”值。由于(模型的)transform 属性从未更改,因此它们是相同的。

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

因为第二个动画已添加到键 "transform" 的层中,并且层中每个唯一键只能有一个动画。组动画被删除。这使得视图看起来像是跳回了第一个动画的“结束值”。

据我所知,UIKit 正在做什么,然后视图将执行从一个值到相同值的 3 秒动画(即不改变任何内容)。

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

如何解决

真正的问题是这两行:

groupTransform.isRemovedOnCompletion = false
groupTransform.fillMode = kCAFillModeForwards

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

作为一个好的经验法则:在完成时移除动画的“唯一”时间是,如果它正在为视图/层的消失设置动画,并且视图/层在完成后被移除.


解决此问题的一种方法是删除这两行,然后在将动画组添加到图层之前还必须将视图(或图层)的变换更新为新的 transform 值。

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

尽可能坚持使用 UIView 动画具有更新模型值的好处。

【讨论】:

  • 这是一个很棒且非常彻底的答案。非常感谢!
【解决方案2】:

David Rönnqvist 为您提供了一个非常好的描述,但如果您想要一个非常非常简单的选项,“将其动画化回原样” ...

选项 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 - 如果您想在另一个操作中“发回”,例如:

@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

    })

}

【讨论】:

    猜你喜欢
    • 2015-12-16
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 2011-12-08
    • 1970-01-01
    • 2015-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多