Recently i’ve started making a lot of use of CAShapeLayers as the backing for a UIView subclass. The main reason for this is it gives me a reusable high performance background which is especially useful as I find a lot of my apps usually have similar looking views with different content.
This is all well and good providing you don’t do any animation however as soon as you start animating things go wrong.
Typically what will happen is when the UIView frame is changed in a UIView animation block the backing layer gets instantly updated resulting in it jumping to the new position. Which is not good.
Nick Lockwood has written an article here which talks about backing layers and overriding the actionForLayer:forKey: in the UIView subclass to animate the backing layers properties correctly during a UIView animation block. This isn’t convenient for example if the backing layer is used in more then one view as it will require copying the actionForLayer:forKey: to multiple places or creating a subclass of UIView which then must be subclassed every time you want to use it.
The good news is actionForLayer:forKey: actually calls actionForKey: in the backing layer and it’s in the actionForKey: method where we can intercept these calls and provide a new animation for when the path is changed.
An example layer written in swift is as follows:
class AnimatedBackingLayer: CAShapeLayer { override var bounds: CGRect { didSet { if !CGRectIsEmpty(bounds) { path = UIBezierPath(roundedRect: CGRectInset(bounds, 10, 10), cornerRadius: 5).CGPath } } } override func actionForKey(event: String) -> CAAction? { if event == "path" { if let action = super.actionForKey("backgroundColor") as? CABasicAnimation { let animation = CABasicAnimation(keyPath: event) animation.fromValue = path // Copy values from existing action animation.autoreverses = action.autoreverses animation.beginTime = action.beginTime animation.delegate = action.delegate animation.duration = action.duration animation.fillMode = action.fillMode animation.repeatCount = action.repeatCount animation.repeatDuration = action.repeatDuration animation.speed = action.speed animation.timingFunction = action.timingFunction animation.timeOffset = action.timeOffset return animation } } return super.actionForKey(event) } }