动画CALayer的安全方法

Posted

技术标签:

【中文标题】动画CALayer的安全方法【英文标题】:safe way to animate CALayer 【发布时间】:2020-09-21 05:36:51 【问题描述】:

当我在寻找 CALayer 动画时,我找到了这样的解决方案:

let basicAnimation = CABasicAnimation(keyPath: "opacity")
basicAnimation.fromValue = 0
basicAnimation.toValue = 1
basicAnimation.duration = 0.3
add(basicAnimation, forKey: "opacity")

但是fromValue和toValue都是Any类型,作为key我们可以使用任意字符串,这是不安全的。有没有更好的方法来使用最新的 Swift 功能?

【问题讨论】:

【参考方案1】:

我想出了使用非常简单的解决方案:

layer.animate(.init(
    keyPath: \.opacity,
    value: "1", // this will produce an error
    duration: 0.3)
)
layer.animate(.init(
    keyPath: \.opacity,
    value: 1, // correct
    duration: 0.3)
)
layer.animate(.init(
    keyPath: \.backgroundColor,
    value: UIColor.red, // this will produce an error
    duration: 0.3,
    timingFunction: .init(name: .easeOut),
    beginFromCurrentState: true)
)
layer.animate(.init(
    keyPath: \.backgroundColor,
    value: UIColor.red.cgColor, // correct
    duration: 0.3,
    timingFunction: .init(name: .easeOut),
    beginFromCurrentState: true)
)

而解决方案代码是:

import QuartzCore

extension CALayer 
    struct Animation<Value> 
        let keyPath: ReferenceWritableKeyPath<CALayer, Value>
        let value: Value
        let duration: TimeInterval
        let timingFunction: CAMediaTimingFunction? = nil
        let beginFromCurrentState = false
    
    
    @discardableResult func animate<Value>(
        _ animation: Animation<Value>,
        completionHandler: (() -> Void)? = nil)
    -> CABasicAnimation?
    
        CATransaction.begin()
        CATransaction.setCompletionBlock(completionHandler)
        defer 
            // update actual value with the final one
            self[keyPath: animation.keyPath] = animation.value
            CATransaction.commit()
        
        guard animation.duration > 0 else  return nil 
        let fromValueLayer: CALayer
        if animation.beginFromCurrentState, let presentation = presentation() 
            fromValueLayer = presentation
         else 
            fromValueLayer = self
        
        let basicAnimation = CABasicAnimation(
            keyPath: NSExpression(forKeyPath: animation.keyPath).keyPath
        )
        basicAnimation.timingFunction = animation.timingFunction
        basicAnimation.fromValue = fromValueLayer[keyPath: animation.keyPath]
        basicAnimation.toValue = animation.value
        basicAnimation.duration = animation.duration
        
        add(basicAnimation, forKey: basicAnimation.keyPath)
        return basicAnimation
    

优点:

在 CALayer 上提供 keyPath 的自动补全功能 值类型取决于 keyPath,因此您将无法设置错误的值 清除代码

缺点:

我们仍然可以选择不可动画的 keyPath

【讨论】:

以上是关于动画CALayer的安全方法的主要内容,如果未能解决你的问题,请参考以下文章

CALayer (CAAnimation) 动画属性绘制错误

如何在更改其边界的动画期间强制重绘 CALayer

UIVIew/CALayer 动画期间的回调

UIView:如何用阴影为 CALayer 框架设置动画?

使用 UIView 或 CALayer 绘制和动画?

动画 CALayer