When I was looking for a CALayer animation, I found solutions like this one:
let basicAnimation = CABasicAnimation(keyPath: "opacity")
basicAnimation.fromValue = 0
basicAnimation.toValue = 1
basicAnimation.duration = 0.3
add(basicAnimation, forKey: "opacity")
But fromValue and toValue are type of Any, and as a key we can use any string, which is not safe. Is there a better way to do so using newest Swift features?
I came up with the solution where usage is pretty simple:
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)
)
And the solution code is:
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
}
}
Pros:
Cons: