Links Animating Layer Content - Core Animation Programming Guide ## Setting Up the Playground with a View Controller We want some interactivity this time, so we’ll use a view controller. import UIKit import PlaygroundSupport final class ProgressView: UIView { var progress: CGFloat = 0 } final class ViewController: UIViewController { let progressView = ProgressView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white progressView.progress = 0.31 progressView.backgroundColor = .clear progressView.frame = view.bounds progressView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(progressView) } } PlaygroundPage.current.liveView = ViewController() Here we have a custom view that has a progressView as a subview. We'll tap on the main view to increment the progress of the progress view. // in viewDidLoad... let tap = UITapGestureRecognizer(target: self, action: #selector(increment)) view.addGestureRecognizer(tap) And then we can use that handler to increment a new property: @objc private func increment() { progressView.progress += 0.1 } Creating a Custom Layer-backed View We want to do the drawing with a custom CALayer, so we'll create that first: private final class ProgressLayer: CALayer { var progress: CGFloat = 0 { didSet { setNeedsDisplay() } } private let fillColor = UIColor.blue.cgColor override init() { super.init() } override init(layer: Any) { super.init(layer: layer) guard let progressLayer = layer as? ProgressLayer else { return } progress = progressLayer.progress } override func draw(in context: CGContext) { context.setFillColor(fillColor) var rect = bounds rect.size.width *= progress context.fill(rect) } required init?(coder aDecoder: NSCoder) { fatalError() } } We want to use this layer as our view's layer, and also forward any progress changes over to the layer. var progress: CGFloat { get { return progressLayer?.progress ?? 0 } set { progressLayer?.progress = newValue } } override class var layerClass: AnyClass { return ProgressLayer.self } private var progressLayer: ProgressLayer? { return layer as? ProgressLayer } Supporting Animations In order to support animations, we'll have to make a few changes. First, our property needs to by marked as @NSManaged: class ProgressLayer : CALayer { @NSManaged var progress: CGFloat ... } Then we need to tell the system that when the progress changes, we need to redraw: override class func needsDisplay(forKey key: String) -> Bool { if key == "progress" { return true } return super.needsDisplay(forKey: key) } And we need return an CAAction for when this key changes, which will likely be an animation: override func action(forKey key: String) -> CAAction? { if key == "progress" { let animation = CABasicAnimation(keyPath: key) animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) animation.fromValue = presentation()?.value(forKey: key) return animation } return super.action(forKey: key) } Note that we use the presentation() instead of the progress on self directly. CALayers have a presentation layer which hold the current values that are being displayed, whereas the layer just contains the starting/ending values for any animation. Consider for instance moving a box’s X position from 0 to 100. The layer would get this value set to 100 immediately, but the presentation layer would have its value reflect the in between animation state. By doing this we can ensure our animation starts from wherever it currently is on screen, rather than resetting it back to the start position. We’ll have to use this presentation value as well when we are drawing: let progress = presentation()?.progress ?? 0 var rect = bounds rect.size.width *= progress context.fill(rect)