I want to animate a CALAyer
so that it looks like it appears out of nowhere. I thought I could first set the transform of the layer to "scale to x: 0, y: 0". Then do an animation to set the transform to "scale to x: 1, y: 1", which is the identity. I thought this would make the layer appear to scale up from nothing. However, when I tried to implement this, the layer appeared immediately without animation.
MCVE:
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
backgroundColor = .clear
clipsToBounds = false
setupLayers()
}
var squareLayer: CAShapeLayer!
private func setupLayers() {
layer.sublayers?.forEach { $0.removeFromSuperlayer() }
squareLayer = CAShapeLayer()
layer.addSublayer(squareLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
drawLayers()
}
func drawLayers() {
squareLayer.fillColor = UIColor.red.cgColor
squareLayer.path = UIBezierPath(rect: bounds).cgPath
squareLayer.frame = bounds
}
}
Then in my VC:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
I thought this was because the first setAffineTransform
call is also animated (I didn't know exactly why that would have an effect, but I suspected that might have an effect), so I tried moving the second call into the completion block:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
CATransaction.begin()
CATransaction.setCompletionBlock {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
CATransaction.commit()
}
However, the output is the same. On the other hand, if I wait for a bit after the first setAffineTransform
call, then it works:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
DispatchQueue.main.asyncAfter(deadline: .now() 0.1) {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
}
I don't think this is how I'm supposed to solve this problem though. Presumably, during that time, some lifecycle method gets called (I don't know which), and what I'm supposed to do is to do the second setAffineTransform
call after that lifecycle method... (?)
I know I can use CABasicAnimation
for this instead, but I don't like how I need to use a delegate to detect the end of animations. This makes many things very cumbersome for me. Since delaying by 0.1 seconds worked, this can't be impossible with CATransaction
s, right?
How can I make the CALayer
appear using an animation?
CodePudding user response:
On the other hand, if I wait for a bit
That's correct. A layer must be part of the render tree in order to be animated, so you cannot add a layer and animate it as part of the same transaction. Thus your async call waits for the current transaction to commit and the next one to start before attaching the animation, and is in fact a totally correct solution.
Another possibility is to call CATransaction.flush()
after adding the layer; this effectively commits the current transaction early.