Irbisa · Шпаргалка

Анимации и Core Animation

CALayer, неявные анимации, UIViewPropertyAnimator, переходы, PhaseAnimator, KeyframeAnimator

Senior-разработчик · 20 вопросов

  1. 01

    Эта точка уезжает в x = 300 и остаётся там. Что выведет print через секунду, и почему перенос в 100 ничего не меняет на экране?

    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}

    Выведет 40. Анимация изменила только presentation layer, а fillMode = .forwards вместе с isRemovedOnCompletion = false замораживает эту картинку поверх модели, которая никуда не двигалась, и пока замороженная анимация висит на слое, она перекрывает любое новое значение модели.

  2. 02

    Почему изменение свойства у отдельного CALayer анимируется само, а то же изменение у слоя, на котором стоит вью, просто прыгает?

    Оба изменения проходят один и тот же поиск action и получают разные ответы: у отдельного слоя нет делегата, и поиск доходит до анимации по умолчанию на 0,25 с, а слой вью спрашивает свой UIView, который возвращает NSNull (без анимации), если изменение сделано не внутри блока анимации.

    // 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

    Что такое CATransaction, и как запретить анимацию для одного изменения слоя?

    CATransaction представляет собой пачку, в которой изменения слоёв уходят в render server: она задаёт им длительность анимации, тайминг-функцию и completion и коммитит их вместе.

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

    Где будет нарисована карточка с трансформом a и с трансформом b, и что каждый раз выведет print?

    // 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?

    С a карточка рисуется на 100 pt правее, с центром в (300, 200); с b рисуется на 100 pt ниже, в (200, 300); в обоих случаях она повёрнута на 90°.

  5. 05

    Какие подклассы CALayer вы используете, и почему анимированное кольцо прогресса лучше делать на CAShapeLayer, а не через draw(_:)?

    Специализированные слои (CAShapeLayer, CAGradientLayer, CATextLayer, CAReplicatorLayer, CAEmitterLayer) описывают содержимое, которое render server рисует сам.

    // 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

    Что такое CADisplayLink, когда он нужен вместо Core Animation, и как получить от него 120 Гц на iPhone с ProMotion?

    CADisplayLink представляет собой таймер, синхронизированный с дисплеем: он вызывает вас раз в кадр, чтобы вы сами посчитали значение для этого кадра.

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

    Что даёт UIViewPropertyAnimator по сравнению с UIView.animate, и как управлять им из pan-жеста?

    UIView.animate работает по принципу «запустил и забыл»; UIViewPropertyAnimator представляет собой объект, который вы храните, поэтому его можно поставить на паузу, скрабить fractionComplete, развернуть, добавлять анимации и completion на ходу и завершить с новым таймингом, а это то, что нужно интерактивному и прерываемому UI.

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

    Найдите баг: по нажатию на Filters панель должна плавно выезжать, но она сразу прыгает на полную высоту без анимации.

    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    }  }}

    Изменение constant само по себе ничего не двигает: оно только помечает layout как требующий обновления.

  9. 09

    Как реализовать кастомный переход между view controller, и кто добавляет и убирает вью в контейнере?

    Верните объект, реализующий UIViewControllerAnimatedTransitioning, из делегата навигации или 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

    Когда в SwiftUI на самом деле срабатывает .transition, и почему вью иногда просто появляется без него?

    Transition срабатывает, только когда вью вставляется в иерархию или удаляется из неё (меняется её identity), и это изменение анимировано.

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

    Как выполнить код, когда анимация в SwiftUI закончилась, и как запустить одну анимацию после другой?

    С iOS 17 withAnimation(_:completionCriteria:_:completion:) вызывает замыкание, когда анимация завершена, а цепочку строят, запуская следующий withAnimation внутри него.

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

    Чем phaseAnimator отличается от keyframeAnimator, и когда достаточно анимации с repeatForever?

    phaseAnimator переводит все свойства по списку состояний вместе; keyframeAnimator даёт каждому свойству свою шкалу кейфреймов, и шкалы идут параллельно.

    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

    Этот Ring перескакивает к новому прогрессу вместо анимации: почему, и при чём здесь Animatable и animatableData?

    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 интерполирует только то, что тип отдаёт через Animatable.animatableData.

  14. 14

    Чем неявные анимации Core Animation отличаются от явных, и что такое CABasicAnimation, CAKeyframeAnimation и CAAnimationGroup?

    Неявную анимацию Core Animation создаёт сам, когда вы меняете анимируемое свойство слоя; явная представляет собой CAAnimation, который вы собираете и добавляете через 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

    Чем model layer отличается от presentation layer, и почему по вью нельзя тапнуть, пока она анимируется?

    Model layer хранит значения, которые вы установили (во время анимации это уже конечные), а presentation() возвращает копию с тем, что на экране в этом кадре.

    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

    Как работает matchedGeometryEffect, и почему карточка, которая должна разворачиваться в экран деталей, вместо этого просто гаснет и прыгает?

    matchedGeometryEffect не морфит содержимое: он берёт frame вью, помеченной как источник, и применяет его к другой вью с тем же id и 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

    Как передать скорость жеста в момент отпускания в пружинную анимацию, и что означают duration и bounce у пружины?

    Передайте скорость пальца как начальную скорость пружины, и тогда движение продолжится, а не начнётся заново из состояния покоя.

    // 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

    Как сделать кастомный переход интерактивным, и что должно произойти, если пользователь отменил его на полпути?

    Верните из делегата interaction controller (обычно UIPercentDrivenInteractiveTransition) и управляйте им из жеста через update(_:), а затем finish() или 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

    Какие свойства слоя дёшево анимировать, и что заставляет render server переходить на offscreen rendering?

    Анимировать transform, opacity, position или bounds уже отрисованного слоя дёшево: приложение коммитит один раз, а render server интерполирует каждый кадр.

  20. 20

    Если анимация дёргается на реальном iPhone, как понять, кто опаздывает: commit вашего приложения или render server?

    Профилируйте release-сборку на устройстве шаблоном Animation Hitches в Instruments: он относит каждый hitch либо к фазе commit в вашем процессе, либо к фазе render в render server.