Перейти к содержанию

3. Основные концепции анимаций в SwiftUI


Изучение данного блока предполагает предварительное знание синтаксиса языка Swift. Для успешного освоения этого материала, необходимо иметь базовое понимание синтаксиса языка Swift. Это включает в себя знание основных структур данных, операторов, циклов, функций, абстракций и других ключевых элементов языка. Без этих фундаментальных знаний будет сложно понять более сложные концепции и примеры, которые будут рассматриваться в данном блоке.


Анимация является ключевым элементом современного интерфейса пользователя, создавая ощущение живости и отзывчивости приложения. SwiftUI предоставляет мощные инструменты для создания анимаций, и в этом руководстве мы рассмотрим различные методы их реализации, их преимущества и недостатки.

Основные концепции

В SwiftUI существует несколько основных методов и модификаторов для создания анимаций:

  1. .animation(_:)
  2. .animation(, value:)
  3. withAnimation
  4. AnyTransition
  5. AnimatableModifier
  6. Работа с векторными данными (VectorArithmetic, AnimatableData)
  7. .matchedGeometryEffect()
  8. .transformEffect()

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

Простые анимации: Модификаторы .animation(_:) и .animation(, value:)

Модификатор .animation(_:)

Модификатор находится в статусе Deprecated, но его до сих пор можно встретить в проектах

.animation(_:) — это базовый модификатор, который применяется ко всей вьюхе и анимирует все изменения её состояния.

struct SimpleAnimationView: View {
    @State private var isScaled = false

    var body: some View {
        Circle()
            .scaleEffect(isScaled ? 2.0 : 1.0)
            .animation(.easeInOut(duration: 2.0))
            .onTapGesture {
                isScaled.toggle()
            }
    }
}
Animation1.gif

Преимущества

  • Простота применения. Весь код анимации сосредоточен в одном месте.
  • Подходит для случаев, когда изменения состояния должны быть анимированы одновременно.

Недостатки

  • Анимация применяется ко всем изменениям, что может привести к неожиданным эффектам.
  • Ограниченный контроль над анимацией отдельных изменений.

Реализация*

*️⃣ SwiftUI обрабатывает изменения состояния и автоматически применяет анимацию к связанным вьюхам. Внутри фреймворка создаются временные слоты, в которых изменения визуально трансформируются с использованием тайминга анимации. Это достигается благодаря внутреннему API, которое интерполирует значения между текущим и целевым состоянием.

Модификатор .animation(, value:)

.animation(, value:) — более избирательный способ управления анимациями. Анимация выполняется только при изменении конкретного значения.

struct ValueBasedAnimationView: View {
    @State private var scale: CGFloat = 1.0
    @State private var colorToggle = false

    var body: some View {
        VStack {
            Circle()
                .fill(colorToggle ? Color.red : Color.blue)
                .frame(width: 100, height: 100)
                .scaleEffect(scale)
                .animation(.easeInOut(duration: 1.0), value: scale)
                .animation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 1.0), value: colorToggle)

            Button("Change Scale") {
                scale = scale == 1.0 ? 2.0 : 1.0
            }

            Button("Toggle Color") {
                colorToggle.toggle()
            }
        }
    }
}
Animation2.gif

Здесь анимация запускается только при изменении значений scale или colorToggle.

Преимущества

  • Точный контроль над тем, какие изменения должны быть анимированы.
  • Улучшенная производительность, так как анимация применяется только к конкретным изменениям.

Недостатки

  • Может потребоваться больше кода для управления различными состояниями.
  • При множественных изменениях одновременно возможно несоответствие анимаций.

Реализация*

*️⃣ Этот модификатор использует принцип дифференциации состояний. SwiftUI отслеживает изменения указанных значений, и если одно из них изменяется, анимация автоматически применяется к связанным визуальным элементам. Внутри фреймворка используется механизм KVO (Key-Value Observing) для отслеживания этих изменений.

Контекстная анимация: withAnimation

withAnimation позволяет оборачивать изменения состояния в блок анимации. В отличие от .animation(_:), withAnimation позволяет анимировать конкретные действия, а не все изменения вьюхи.

struct ContextualAnimationView: View {
    @State private var isVisible = false

    var body: some View {
        VStack {
            Text("Hi world!")

            if isVisible {
                Text("Hello world!")
                    .frame(maxWidth: .infinity)
            }
        }
        .onTapGesture {
            withAnimation(.easeInOut) {
                isVisible.toggle()
            }
        }
    }
}
Animation3.gif

Преимущества

  • Анимация применяется только к конкретным действиям, что повышает предсказуемость поведения.
  • Легче управлять и синхронизировать анимации.

Недостатки

  • Требует явного указания, какие действия должны быть анимированы.
  • Может усложнить код при множественных состояниях и действиях.

Реализация*

*️⃣ withAnimation работает, оборачивая изменения состояния в транзакцию, которая передает анимационные параметры внутрь фреймворка. Эти параметры затем используются для создания анимации перехода от одного состояния к другому. SwiftUI управляет этими транзакциями, обеспечивая синхронное выполнение анимаций.

Переходы между состояниями: AnyTransition и .transition()

AnyTransition используется для анимации появления и исчезновения вьюх. Это мощный инструмент для создания плавных переходов между состояниями интерфейса.

struct TransitionExampleView: View {
    @State private var isVisible = true

    var body: some View {
        VStack {
            Text("Hi World!")

            if isVisible {
                Rectangle()
                    .fill(Color.blue)
                    .frame(width: 200, height: 200)
                    .transition(.scale)
            }
        }
        .onTapGesture {
            withAnimation {
                isVisible.toggle()
            }
        }
    }
}
Animation4.gif

Виды предустановленных переходов:

  • .slide: Сдвиг вьюхи в сторону.
  • .scale: Масштабирование вьюхи.
  • .opacity: Плавное появление/исчезновение.

Кастомные переходы:

Вы можете создавать свои собственные переходы, комбинируя существующие или создавая полностью уникальные эффекты.

extension AnyTransition {
    static var customScaleAndOpacity: AnyTransition {
        AnyTransition.scale.combined(with: .opacity)
    }
}

Преимущества

  • Простота использования для создания анимаций перехода между состояниями.
  • Поддержка множества предустановленных переходов, таких как .slide, .scale, .opacity.

Недостатки

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

Реализация*

*️⃣ SwiftUI создает временные слепки вьюх для выполнения переходов и управляет визуальной согласованностью между состояниями. Когда вы задаете AnyTransition, SwiftUI создает замороженные копии вьюх до и после анимации и затем интерполирует эти состояния, чтобы создать плавный переход.

Гибкие анимации: AnimatableModifier и работа с векторными данными

Модификаторы анимаций с использованием AnimatableModifier

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

struct AnimatableFontModifier: AnimatableModifier {
    var size: CGFloat

    var animatableData: CGFloat {
        get { size }
        set { size = newValue }
    }

    func body(content: Content) -> some View {
        content
            .font(.system(size: size))
    }
}

struct AnimatableFontView: View {
    @State private var fontSize: CGFloat = 20
    var body: some View {
        Text("Animating Font Size")
            .modifier(AnimatableFontModifier(size: fontSize))
            .onTapGesture {
                withAnimation {
                    fontSize = fontSize == 20 ? 50 : 20
                }
            }
    }
}
Animation5.gif

Преимущества

  • Возможность анимации сложных и нестандартных свойств.
  • Высокая гибкость и возможность создания уникальных анимаций.

Недостатки

  • Более сложный синтаксис.
  • Может потребовать дополнительных усилий по оптимизации.

Реализация*

*️⃣ AnimatableModifier использует протокол AnimatableData, который позволяет SwiftUI интерполировать данные анимации. SwiftUI отслеживает изменения в animatableData, вычисляет промежуточные значения для плавного перехода между состояниями и обновляет визуальные свойства вьюхи соответственно.

Работа с векторными данными (VectorArithmetic, AnimatableData)

Эти протоколы позволяют создавать сложные анимации, работающие с векторными данными, например, анимацию нескольких параметров одновременно.

struct CustomShape: Shape {
    var animatableData: CGFloat

    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.addEllipse(in: CGRect(x: rect.midX - animatableData / 2,
                                   y: rect.midY - animatableData / 2,
                                   width: animatableData,
                                   height: animatableData))
        return path
    }
}

struct VectorAnimationView: View {
    @State private var size: CGFloat = 50

    var body: some View {
        CustomShape(animatableData: size)
            .onTapGesture {
                withAnimation {
                    size = size == 50 ? 150 : 50
                }
            }
    }
}
Animation6.gif

Преимущества

  • Возможность анимации сложных форм и множественных параметров.
  • Высокая гибкость в создании кастомных анимаций.

Недостатки

  • Требует более глубоких знаний для использования.
  • Сложность в управлении и отладке анимаций.

Реализация*

*️⃣ SwiftUI использует протоколы VectorArithmetic и AnimatableData, чтобы разбить данные на составляющие и интерполировать их. Эти протоколы позволяют SwiftUI преобразовывать векторные данные в анимации, которые затем плавно изменяют свойства вьюхи.

Анимации между состояниями: .matchedGeometryEffect()

.matchedGeometryEffect() используется для создания согласованных анимаций между двумя разными состояниями вьюх. Это позволяет создать иллюзию непрерывного движения или трансформации объектов между двумя представлениями.

struct MatchedGeometryEffectExample: View {
    @Namespace private var animationNamespace
    @State private var isExpanded = false

    var body: some View {
        VStack {
            if isExpanded {
                RoundedRectangle(cornerRadius: 25)
                    .matchedGeometryEffect(id: "rectangle", in: animationNamespace)
                    .frame(width: 300, height: 200)
                    .onTapGesture {
                        withAnimation(.spring()) {
                            isExpanded.toggle()
                        }
                    }
            } else {
                RoundedRectangle(cornerRadius: 25)
                    .matchedGeometryEffect(id: "rectangle", in: animationNamespace)
                    .frame(width: 100, height: 100)
                    .onTapGesture {
                        withAnimation(.spring()) {
                            isExpanded.toggle()
                        }
                    }
            }
        }
    }
}
Animation7.gif

Преимущества

  • Позволяет создавать плавные и естественные анимации перехода между разными состояниями интерфейса.
  • Поддержка сложных анимаций между несколькими вьюхами.

Недостатки

  • Требует использования @Namespace, что может усложнить код в более сложных приложениях.
  • Анимации могут быть сложными для отладки при неправильном использовании.

Реализация*

*️⃣ .matchedGeometryEffect() работает, сопоставляя геометрию двух вьюх с использованием уникального идентификатора. SwiftUI затем интерполирует изменения в позиции, размере и форме этих вьюх, создавая плавный переход. Внутри фреймворка используется механизм привязки к анимации, который отслеживает изменения этих вьюх и обновляет представление в реальном времени.

Трансформации с использованием .transformEffect()

.transformEffect() применяется для выполнения матричных трансформаций к вьюхам, таких как перемещение, масштабирование и вращение.

struct TransformEffectExample: View {
    @State private var angle: Angle = .degrees(0)

    var body: some View {
        VStack {
            Rectangle()
                .fill(Color.red)
                .frame(width: 100, height: 100)
                .transformEffect(CGAffineTransform(rotationAngle: CGFloat(angle.radians)))

            Slider(value: $angle.degrees, in: 0...360, step: 1)
                .padding()
        }
        .animation(.easeInOut, value: angle)
    }
}
Animation9.gif

Преимущества

  • Позволяет выполнять сложные трансформации с использованием матричных операций.
  • Поддержка комбинаций нескольких трансформаций для создания уникальных эффектов.

Недостатки

  • Матричные операции могут быть сложными для понимания и использования.
  • Требует внимания к деталям при работе с трансформациями.

Реализация*

*️⃣ .transformEffect() использует матричные преобразования для изменения параметров вьюхи, таких как положение, размер и угол поворота, интерполируя эти значения при анимации. SwiftUI берет матрицы преобразований, применяет их к вьюхе и плавно интерполирует эти матрицы, создавая плавные и согласованные эффекты.

Оптимизация и проблемы с анимацией

Несмотря на простоту и мощь SwiftUI, анимации могут привести к некоторым проблемам, таким как:

  1. Фризы и задержки: Если анимация затрагивает много объектов или выполняет сложные вычисления, это может привести к снижению производительности.

    Решение: Разделите сложные анимации на несколько простых или используйте более быстрые виды анимаций.

  2. Неожиданные эффекты: Стандартные анимации могут иногда вести себя неожиданно, особенно если изменения состояния не синхронизированы.

    Решение: Тщательно управляйте состояниями и используйте withAnimation/animation(, value:) для контроля над анимациями.

  3. Конфликты анимаций: Если несколько анимаций запускаются одновременно, они могут конфликтовать друг с другом.

    Решение: Избегайте одновременных изменений нескольких зависимых состояний.

  4. Проблемы с обновлением состояния: Асинхронные изменения состояния могут приводить к непредсказуемым результатам в анимациях.

    Решение: Минимизируйте количество одновременно изменяемых состояний и используйте транзакции для синхронизации изменений.