动画移回原始位置后未调用 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 上,每个视图总是有一个它拥有和管理的图层。像视图的 frame
或 transform
一样设置 属性 会自动设置图层的 frame
或 transform
。对于图层,设置的这个值有时称为 "model value" 以区别于 "presentation value","presentation value" 是图层在正在进行的动画期间在屏幕上的显示方式。实际上这意味着两件事:
如果在视图使用 UIView 动画进行动画处理时尝试检查图层位置的值,则 position
值将成为最终值,该值在动画块。要获得屏幕上显示的 "current" 值,必须查看图层的表示层的值。
如果向图层添加显式 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
})
}
目前我正在使用一个按钮来激活一个 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 上,每个视图总是有一个它拥有和管理的图层。像视图的 frame
或 transform
一样设置 属性 会自动设置图层的 frame
或 transform
。对于图层,设置的这个值有时称为 "model value" 以区别于 "presentation value","presentation value" 是图层在正在进行的动画期间在屏幕上的显示方式。实际上这意味着两件事:
如果在视图使用 UIView 动画进行动画处理时尝试检查图层位置的值,则
position
值将成为最终值,该值在动画块。要获得屏幕上显示的 "current" 值,必须查看图层的表示层的值。如果向图层添加显式 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
})
}