UIViewPropertyAnimator是iOS中的一个动画类,用于创建和管理视图动画。它提供了一种强大且灵活的方式来创建自定义的交互式动画:可以定义动画的起始状态、结束状态和持续时间,还可以通过控制动画的进度来实现交互性。
但是使用UIViewPropertyAnimator的过程中我发现了挺多坑,很多方法的调用不够安全,例如调用了stopAnimation(false)之后如果不调用finishAnimation(at: xxx)在释放时就会崩溃,又或者直接调用finishAnimation(at: xxx)也会立马崩溃等,这些都是会导致崩溃的坑啊。
虽说API已经有注释说明了,但概括得不够全面,加上这些API内部并没有做任何防护机制,一旦用错直接崩溃处理,即便可能是出于性能优化的考虑,可这给我的感觉就是它做得不够友好。
再说了,也并非所有人都会先一字一句地去理解注释写的啥再使用吧,实践出真知,总要去踩坑的。
为了防止以后忘了再次踩坑,我把个人总结的这一部分使用UIViewPropertyAnimator的坑和注意点在这里分享一下。
坑(Crash)
首当其冲是会导致崩溃的坑,一般都是在某个界面创建了UIViewPropertyAnimator的实例对象,当退出该界面时引发的崩溃:
#1
如果退出该界面时(其实不只是退出界面的时候,实际上是animator要被销毁时),若animator的「状态为active并且暂停中」,或者「状态为stopped」,会直接崩溃!
"It is an error to release a paused or stopped property animator. Property animators must either finish animating or be explicitly stopped and finished before they can be released."
大概意思就是animator必须完结后才能被销毁,解决方法:
-
如果
animator的「状态为active并且暂停中」,退出界面时,需要调用stopAnimation(true)或stopAnimation(false)+finishAnimation(at: xxx);- 另外如果
pausesOnCompletion为false,也可以调用startAnimation(只要启动了,即便在动画期间被销毁,animator也会自动完结此次动画),因为pausesOnCompletion为true的效果就是当动画完成时变为暂停状态,而非完结状态。
- 另外如果
-
如果「状态为
stopped」,只需也只能调用finishAnimation(at: xxx)。
deinit {
// 1.如果状态为active并且暂停中
animator.stopAnimation(true)
// 或者
animator.stopAnimation(false)
animator.finishAnimation(at: .current)
// 或者如果pausesOnCompletion为false
animator.startAnimation()
// 2.如果状态为stopped
animator.finishAnimation(at: .current)
}
PS:stopAnimation(true)效果等同于stopAnimation(false)+finishAnimation(at: .current)
#2
如果animator的状态已经为stopped(调用了stopAnimation(false)),再次调用stopAnimation(true/false),会直接崩溃!
"Animator <UIViewPropertyAnimator(0x600003e24200) [stopped] interruptible> is already stopped!"
不能连续stop!
#3
如果animator非stopped状态下直接调用finishAnimation(at: xxx),会直接崩溃!
"finishAnimationAtPosition: should only be called on a stopped animator!"
📢 注意:如果animator从未开启,也就是状态为inactive(没调用过startAnimation/pauseAnimation,也没设置过fractionComplete),调用stopAnimation(false)+finishAnimation(at: xxx)也会崩溃。
- 这是因为在未开启状态,调用
stopAnimation(true/false)是无效的,状态不会变为stopped,相当于直接调用了finishAnimation(at: xxx),所以才崩溃。
注意点
以下是平时使用UIViewPropertyAnimator的一些注意点,也包括了上面的坑:
#1
animator初始化时状态为inactive,只要「调用过startAnimation」或「调用过pauseAnimation」或「设置过fractionComplete」,状态就变为active,之后动画正在运行、正在暂停、还没结束的情况下状态都为active。
#2
调用pauseAnimation会暂停动画,状态还是active,可以修改fractionComplete(动画进度),再次调用startAnimation会继续动画。
#3
调用stopAnimation(true),状态变为inactive,fractionComplete变为0并且无法修改,再次调用startAnimation无效,但状态会变为active,而且能修改fractionComplete。【📌animator死了】
#4
调用stopAnimation(false),状态变为stopped,保持动画开启前的fractionComplete(如果调用了startAnimation则是调用前的fractionComplete,不是执行动画过程中变化的fractionComplete)并且无法修改,再次调用startAnimation无效,状态还是stopped。【📌animator死了】
#5
如果已经调用了stopAnimation(false),接着调用stopAnimation(false)或stopAnimation(true)就会崩溃,不能连续stop!
#6
动画开启并执行完成后:
- 如果
pausesOnCompletion为false,会调用addCompletion的闭包,state会变为inactive,fractionComplete变为0,此时无法修改fractionComplete,再次调用startAnimation无效,但状态会变为active,而且能修改fractionComplete。【相当于 #3,📌animator死了】 - 如果
pausesOnCompletion为true,不会调用addCompletion的闭包,状态还是active,fractionComplete变为1,可以继续修改fractionComplete(动画进度),再次调用startAnimation会继续动画。【相当于完成时调用了pauseAnimation】
#7
当状态为active时,app进入后台再回到前台:
- 如果
pausesOnCompletion为false,动画会直接完成:会调用addCompletion的闭包,状态变为inactive,fractionComplete变为0。【相当于 #6.1,📌animator死了】 - 如果
pausesOnCompletion为true,动画会暂停:不会调用addCompletion的闭包,状态还是active,保持当前fractionComplete,再次调用startAnimation会继续动画【相当于 #6.2,animator自动调用了pauseAnimation】
#8
如果animator已经启动(状态为active),必须要先stopAnimation(false)才能调用finishAnimation(at: xxx),也就是说只有状态为stopped才能调用finishAnimation(at: xxx),否则直接调用finishAnimation(at: xxx)会直接崩溃!
- 调用
finishAnimation(at: xxx)会触发addCompletion的闭包,即便pausesOnCompletion为true也会触发,相当于强制完成动画, stopAnimation(false)+finishAnimation(at: xxx)等同于调用可以选择最终进度(start、current、end)的stopAnimation(true)。【相当于 #3,📌animator死了】- 相反,
stopAnimation(true)等同于stopAnimation(false)+finishAnimation(at: .current)。【📌animator死了】
#9
如果animator的状态是从active变为inactive的(也就是启动后调用了stopAnimation(false)+finishAnimation(at: xxx)或stopAnimation(true)),之后怎么调用finishAnimation(at: xxx)都不会有动静,但是,如果接着调用startAnimation,虽然无效,但状态会变为active,之后再调用finishAnimation(at: xxx)就会崩溃。【相当于 #8,非stopped状态下就调用finishAnimation(at: xxx)】
#10
如果animator从来没启动过(状态还没变为active),调用stopAnimation(true)或stopAnimation(false)是无效的,是可以继续开启动画的。因此如果animator从来没启动,就调用stopAnimation(false)+finishAnimation(at: xxx)是无法移除动画的,而且会崩溃,因为stopAnimation(false)执行无效,接着就调用finishAnimation(at: xxx)。【相当于 #8,非stopped状态下就调用finishAnimation(at: xxx)】
关于【📌animator死了】
只要animator启动后再停止,也就是状态先从inactive变为active,再从active变为stopped或inactive后,我都将其视为animator死了,
animator一旦死了,将无法再次启动动画,调用任何方法都不会再起作用,所以死了就不要再使用该animator了,免得状态和fractionComplete的错乱导致判断失误(例如上面的 #3、 #6.1、 #9)。
- 感觉跟自定义Thread一个道理,任务完成后就是一具尸体。
若想启动新动画,应该是直接新建一个animator去使用。
在UITableView、UICollectionView上失效的情况
如果在cell上使用了UIViewPropertyAnimator,只要cell被复用过,它的animator就会永久失效(整个app的生命周期内):animator的方法能正常运作,但实际上已经没有动画效果(动画完成的状态),跟死了差不多情况。
🌰🌰🌰 使用animator设置10%模糊度:

PS:手动对cell调用prepareForReuse方法同样也会让animator永久失效。
不过可能苹果就是不希望在这些复用列表中使用UIViewPropertyAnimator吧,毕竟对性能会有一定的负担。但是如果非得要用,也不是不可以,只要在cell每次复用后重置一个新的animator即可:
// MARK: - UICollectionViewCell
class JPCell: UICollectionViewCell {
......
private let effect = UIBlurEffect(style: .light)
private let effectView = UIVisualEffectView(effect: nil)
private var animator: UIViewPropertyAnimator?
......
deinit {
removeAnimator()
}
private func removeAnimator() {
guard let animator else { return }
if animator.state == .stopped {
animator.finishAnimation(at: .current)
} else {
animator.stopAnimation(true)
}
self.animator = nil
}
func resetAnimator() {
removeAnimator()
effectView.effect = nil
let animator = UIViewPropertyAnimator(duration: 10, curve: .linear, animations: { [weak self] in
self?.effectView.effect = self?.effect
})
animator.pausesOnCompletion = true
animator.fractionComplete = 0.1
self.animator = animator
}
}
// MARK: - UICollectionViewDataSource
extension JPCollectionViewController: UICollectionViewDataSource {
......
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "JPCell", for: indexPath) as! JPCell
// 需要在Runloop下一个循环中重置,否则还是会失效。
DispatchQueue.main.async {
cell.resetAnimator()
}
return cell
}
......
}
效果如下:

最后
针对以上UIViewPropertyAnimator的坑和注意点,后续将整合起来封装成一个安全的animator,然后同步到我之前写的JPBlurView中去(顺便也适配一下在UITableView、UICollectionView上的情况),如有需要,敬请期待。
That is all, thanks.