Irbisa · Cheatsheet

Animations & Core Animation

CALayer, implicit animations, UIViewPropertyAnimator, transitions, PhaseAnimator, KeyframeAnimator

Senior Developer · 20 questions

  1. 01

    This dot slides to x = 300 and stays there. What does print show a second later, and why does moving it to 100 change nothing on screen?

    let dot = ballView.layer                 // ballView is centred at x = 40 let slide = CABasicAnimation(keyPath: "position.x")slide.fromValue = 40slide.toValue = 300slide.duration = 0.5slide.fillMode = .forwardsslide.isRemovedOnCompletion = falsedot.add(slide, forKey: "slide") DispatchQueue.main.asyncAfter(deadline: .now() + 1) {  print(dot.position.x)  dot.position.x = 100                   // nothing moves on screen}

    It prints 40.

  2. 02

    Why does changing a property on a standalone CALayer animate by itself, while the same change on a view's backing layer just snaps?

    Both changes go through the same action search and get different answers: a standalone layer has no delegate and falls through to the default 0.25 s animation, while a backing layer asks its UIView, which returns NSNull (no animation) unless the change is inside an animation block.

    // A standalone layer: no delegate, so the default 0.25 s action runslet badge = CALayer()             // added to view.layer in an earlier run-loop turn, already on screenbadge.opacity = 0                 // fades out on its own over 0.25 s // A view's backing layer: the view answers NSNull outside an animation blockview.layer.opacity = 0            // snaps…
  3. 03

    What is CATransaction, and how do you stop a single layer change from animating?

    CATransaction is the batch that carries layer changes to the render server: it sets their animation duration, timing function and completion, and commits them together.

    // No animation for this change: the action search never runsCATransaction.begin()CATransaction.setDisableActions(true)gradient.frame = boundsCATransaction.commit()…
  4. 04

    Where is the card drawn with transform a and with transform b, and what does print show each time?

    // card is 100 x 100, centred at (200, 200), with an identity transformlet a = CGAffineTransform(translationX: 100, y: 0).rotated(by: .pi / 2)let b = CGAffineTransform(rotationAngle: .pi / 2).translatedBy(x: 100, y: 0) card.transform = aprint(card.center)      // what prints, and where is the card drawn? card.transform = bprint(card.center)      // and now?

    With a the card is drawn 100 pt to the right, centred at (300, 200); with b it is 100 pt lower, at (200, 300); both are rotated 90°.

  5. 05

    Which CALayer subclasses do you use, and why build an animated progress ring with CAShapeLayer instead of draw(_:)?

    Specialised layers (CAShapeLayer, CAGradientLayer, CATextLayer, CAReplicatorLayer, CAEmitterLayer) describe content the render server draws itself.

    // draw(_:) version: 300 x 300 pt at 3x = 900 x 900 px x 4 B, about 3.2 MB, repainted per change // Shape layer version: no bitmap, and progress is one animatable propertylet ring = CAShapeLayer()ring.frame = CGRect(x: 0, y: 0, width: 300, height: 300)ring.path = UIBezierPath(ovalIn: ring.bounds.insetBy(dx: 4, dy: 4)).cgPath…
  6. 06

    What is CADisplayLink, when do you need it instead of Core Animation, and how do you get 120 Hz from it on a ProMotion iPhone?

    CADisplayLink is a timer synchronised with the display: it calls you once per frame so you can compute that frame's value yourself.

    final class WaveformView: UIView {  private var link: CADisplayLink?  private var startTime: CFTimeInterval = 0  private var phase: Double = 0   override func didMoveToWindow() {…
  7. 07

    What does UIViewPropertyAnimator give you over UIView.animate, and how do you drive one from a pan gesture?

    UIView.animate is fire-and-forget; UIViewPropertyAnimator is an object you keep, so you can pause it, scrub fractionComplete, reverse it, add animations or completions while it runs and finish it with new timing, which is what interactive, interruptible UI needs.

    final class SheetController: UIViewController {  let sheet = UIView()  private var animator: UIViewPropertyAnimator?   @objc func handlePan(_ pan: UIPanGestureRecognizer) {    let travel = sheet.bounds.height…
  8. 08

    Find the bug: tapping Filters should slide the panel open, but it jumps to full height with no animation.

    final class FiltersViewController: UIViewController {  let panel = UIView()  private var panelHeight: NSLayoutConstraint!   // panel.heightAnchor, starts at 0  private var isExpanded = false   @objc func toggleFilters() {    isExpanded.toggle()    UIView.animate(withDuration: 0.3) {      self.panelHeight.constant = self.isExpanded ? 320 : 0    }  }}

    Changing constant moves nothing by itself: it only marks the layout as needing an update.

  9. 09

    How do you implement a custom view controller transition, and who adds and removes the views in the container?

    Return an object conforming to UIViewControllerAnimatedTransitioning from the navigation or transitioning delegate.

    final class SlideAnimator: NSObject, UIViewControllerAnimatedTransitioning {  let operation: UINavigationController.Operation  init(operation: UINavigationController.Operation) { self.operation = operation }   func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.32 }…
  10. 10

    When does a SwiftUI .transition actually run, and why does a view sometimes just appear without it?

    A transition runs only when a view is inserted into or removed from the hierarchy (its identity changes) and that change is animated.

    struct Inbox: View {  @State private var showBanner = false  @State private var items = Message.samples   var body: some View {    VStack {…
  11. 11

    How do you run code when a SwiftUI animation finishes, and how do you chain one animation after another?

    Since iOS 17, withAnimation(_:completionCriteria:_:completion:) calls a closure when the animation is done, and you chain by starting the next withAnimation inside it.

    struct SavedToast: View {  @State private var visible = false  @State private var offset: CGFloat = 0  let onFinished: () -> Void   var body: some View {…
  12. 12

    What is the difference between phaseAnimator and keyframeAnimator, and when is a repeatForever animation enough?

    phaseAnimator steps through a list of states, moving every property to the next state together; keyframeAnimator gives each property its own timeline of keyframes, running in parallel.

    struct Pose { var scale = 1.0, angle = Angle.zero, lift = 0.0 } struct BellBadge: View {  @State private var taps = 0   var body: some View {…
  13. 13

    This Ring jumps to the new progress instead of animating: why, and what do Animatable and animatableData have to do with it?

    struct Ring: Shape {  var progress: Double                    // 0...1   func path(in rect: CGRect) -> Path {    var path = Path()    path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2,                startAngle: .degrees(-90), endAngle: .degrees(-90 + 360 * progress),                clockwise: false)    return path  }} // Ring(progress: done).stroke(.teal, lineWidth: 8)//   .animation(.easeOut(duration: 0.6), value: done)

    SwiftUI interpolates only what a type exposes through Animatable.animatableData.

  14. 14

    What is the difference between implicit and explicit Core Animation animations, and what are CABasicAnimation, CAKeyframeAnimation and CAAnimationGroup?

    An implicit animation is one Core Animation creates for you when you change an animatable layer property; an explicit one is a CAAnimation you build and attach with layer.add(_:forKey:).

    // Basic: one key path, from one value to anotherlet fade = CABasicAnimation(keyPath: "opacity")fade.fromValue = 1.0fade.toValue = 0.3fade.duration = 0.3badge.opacity = 0.3                    // the model value is yours to set…
  15. 15

    What is the difference between a layer's model and presentation layers, and why can't the user tap a view while it animates?

    The model layer holds the values you set (during an animation, already the end values), while presentation() returns a copy with what is on screen this frame.

    final class GameViewController: UIViewController {  let ball = UIView(frame: CGRect(x: 20, y: 200, width: 44, height: 44))   func launch() {    UIView.animate(withDuration: 2) { self.ball.center.x = 320 }    print(ball.center.x)                               // 320: the model is already there…
  16. 16

    How does matchedGeometryEffect work, and why does a card that should grow into a detail view just fade and snap instead?

    matchedGeometryEffect does not morph content: it takes the frame of the view marked as source and applies it to the other view with the same id and namespace.

    struct CardGallery: View {  @Namespace private var cards                // must outlive both sides of the pair  @State private var expanded: Int?   var body: some View {    ZStack {…
  17. 17

    How do you hand a gesture's release velocity to a spring animation, and what do a spring's duration and bounce mean?

    Pass the finger's velocity as the spring's initial velocity, so the motion continues instead of restarting from rest.

    // UIKit: the release keeps the throwfunc endDrag(_ pan: UIPanGestureRecognizer) {  let target = shouldDismiss ? view.bounds.maxY + sheet.bounds.height / 2 : restingY  let distance = target - sheet.center.y  // initialVelocity is in "remaining distances per second", not points per second  let relative = abs(distance) < 1 ? 0 : pan.velocity(in: view).y / distance…
  18. 18

    How do you make a custom transition interactive, and what must happen when the user cancels it halfway?

    Return an interaction controller (usually UIPercentDrivenInteractiveTransition) from the delegate and drive it from the gesture with update(_:), then finish() or cancel().

    final class DismissInteraction: UIPercentDrivenInteractiveTransition {  private(set) var isDriving = false  weak var presented: UIViewController?   @objc func handle(_ pan: UIPanGestureRecognizer) {    let progress = min(max(pan.translation(in: pan.view).y / 300, 0), 1)…
  19. 19

    Which layer properties are cheap to animate, and what forces the render server into offscreen rendering?

    Animating transform, opacity, position or bounds of an already drawn layer is cheap: the app commits once and the render server interpolates every frame.

  20. 20

    An animation hitches on a real iPhone: how do you find out whether your app's commit or the render server is late?

    Profile a release build on the device with the Animation Hitches template in Instruments: it attributes each hitch to the commit phase in your process or to the render phase in the render server.