Механизм анимации SwiftUI
Механизм анимации SwiftUI

Посетите мой блог www.fatbobman.com[1] Получите лучший опыт чтения

Большинство новичков будут поражены первым SwiftUI Возможность легко добиться различных анимационных эффектов, но через некоторое время использования они найдут SwiftUI В анимации не так легко ориентироваться, как кажется. Разработчикам часто приходится сталкиваться: как двигаться, как двигаться, что можно двигать, почему нет, почему оно так движется, как не допустить его перемещения и т. д. верно SwiftUI Недостаточное понимание логики обработки анимации является основной причиной вышеперечисленных проблем. В этой статье мы постараемся Механизм анимации SwiftUI представлен, чтобы помочь каждому лучше учиться и осваивать работу. SwiftUI анимацию для создания удовлетворительных интерактивных эффектов.

Прежде чем читать эту статью, читатели должны иметь SwiftUI Опыт использования программирования анимации в SwiftUI Иметь определенное понимание основных методов использования анимации. Можно найти в Получите весь код для этой статьи здесь[2]

Что такое анимация SwiftUI?

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

В SwiftUI мы не можем приказать представлению перемещаться из одной позиции в другую. Чтобы добиться вышеуказанного эффекта, нам нужно объявить положение представления в состоянии A и положение состояния B. Когда состояние изменяется с помощью When. A переходит к B, SwiftUI будет использовать указанную функцию алгоритма для предоставления данных, необходимых для создания плавного перехода для конкретного виджета (если виджет анимируемый).

В SwiftUI для реализации анимации требуются следующие три элемента:

  • Функция алгоритма временной кривой
  • Объявите статус (конкретную зависимость) с помощью функции временной кривойсвязанныйиз.
  • все зависит от штата(конкретные зависимости)из Анимируемые части

animationThreeElements

Запутанное название анимации

функция временной кривой

SwiftUI дает функции алгоритма временной кривой запутанное имя — Анимация. Возможно, было бы более уместно назвать ее временной кривой или кривой анимации (например, CAMediaTimingFunction в Core Animation).

Эта функция определяет ритм анимации как временную кривую и преобразует данные начальной точки в данные конечной точки вдоль временной кривой.

Язык кода:javascript
копировать
Text("Hello world")
    .animation(.linear(duration: 0.3), value: startAnimation)
    .opacity(startAnimation ? 0 : 1)

функция временной кривой( Animation )linear(duration:0.3) означает в 0.3 Линейное преобразование данных за секунды (в данном случае из 0 приезжать 1)。

linear_value_sheet

image-20220504111032123

функция временной кривой( Animation )easeInOut(duration:0.3)переписыватьсяизчисловое изменение:

easeInOut_value_sheet

image-20220504110821144

Функция временной кривойз работает только для интерполяционного преобразования данных.,Что касается использования для интерполяции данных, Анимируемые частииз Работа。

VectorArithmetic

Только если оно соответствует VectorArithmetic Тип данных протоколиз можно использовать в функции. временной кривой。SwiftUI По умолчанию предоставляет нам несколько типов данных, таких как: Float, Double, CGFloat. ждать.

Язык кода:javascript
копировать
Text("Hello world")
    .animation(.linear(duration: 0.3), value: startAnimation)
    .opacity(startAnimation ? 0 : 1) // Double тип,соответствовать VectorArithmetic протокол

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

Majid из The magic of Animatable values in SwiftUI[3] В этой статье мы покажем, как сделать так, чтобы пользовательские типы удовлетворяли требованиям. VectorArithmetic протокол.

Свяжите функции временной кривой с состояниями

Только в той или иной форме будет функционировать временной кривая (анимация) и после того, как связана определенная (или множественная) зависимость, SwiftUI будет в штате( Быть связанным из-за зависимостей ) для анимации генерирует данные интерполяции при их изменении. Связанные способы: видмодификатор animation или глобальная функция withAnimation

Аномалии анимации SwiftUI (не соответствующие ожиданиям разработчиков) часто связаны с неправильными методами ассоциации и неправильными местоположениями ассоциации.

Поместите анимацию модификатора в правильное положение.

Код один:

Язык кода:javascript
копировать
@State var animated = false

VStack {
    Text("Hello world")
        .offset(x: animated ? 200 : 0)
        .animation(.easeInOut, value: animated) // animation издляиспользовать домен для текущего уровня вида и его дочерних видов

    Text("Fat")
        .offset(x: animated ? 200 : 0)
}

single_animation_2022-05-04_14.08.25.2022-05-04 14_09_34

Код второй:

Язык кода:javascript
копировать
VStack {
    Text("Hello world")
        .offset(x: animated ? 200 : 0)

    Text("Fat")
        .offset(x: animated ? 200 : 0)
}
.animation(.easeInOut, value: animated)

both_animation_2022-05-04_14.05.54.2022-05-04 14_06_58

Вышеупомянутые два фрагмента кода связаны с тем, что animation Расположение из в коде существования различно, в результате чего существует связанная с ним из зависимостей ( animated)когда происходят изменения,анимацияиз ХОРОШОдляизменил ситуацию。существоватькод одинсередина,только Hello world Произведет плавную анимацию, код 2; Hello world и Fat Оба будут производить плавную анимацию.

То же, что и все SwiftUI Как и извидмодификатор, расположение существующего объекта в коде определяет область применения объекта модификаторизпользования. animation Объект ограничен уровнем, на котором он расположен, и его дочерними узлами.

На приведенные выше два фрагмента кода нет правильного или неправильного ответа.。существовать в некоторых ситуациях,Нам может понадобиться существование, когда определенная зависимость (статус) изменится.,Все зависимости от этого проектаиз内容都产生平滑анимация(Например Код два),существуют в других сценах,可能又仅需部分内容产生平滑анимация(Напримеркод один),проходить Корректирование animation из положения вы можете получить желаемый эффект.

Используйте только версии анимации, в которых указаны конкретные зависимости.

SwiftUI Предусмотрены две версии animation Модификатор:

Язык кода:javascript
копировать
// Версия первая, без указания конкретных зависимостей
func animation(_ animation: Animation?) -> some View

// Версия2, укажите конкретные зависимости, метод useiz используется в предыдущем разделе кода.
func animation<V>(_ animation: Animation?, value: V) -> some View where V : Equatable

Первый способ SwiftUI 3.0 помечен как устаревший, он находится в старой версии SwiftUI Один из виновников аномалий анимации. Эта Версияиз animation 会и Местосуществоватьвидуровеньи Долженвидуровеньиздочерний узелизвсе зависимости进ХОРОШО状态ассоциация。видиэтодочерний узелсерединаизлюбые зависимости меняются,Все будет удовлетворять условиям расчета интерполяции анимации.,并анимация数据传递Даватьделатьиспользоватьв пределах досягаемости(видиэтодочерний узел)из Место有Анимируемые части。

например,由于下面代码серединаиз animation Нет отображения конкретных зависимостей,поэтому,После нажатия кнопки,Положение и цвет обеспечат плавную анимацию.

Язык кода:javascript
копировать
struct Demo2: View {
    @State var x: CGFloat = 0
    @State var red = false
    var body: some View {
        VStack {
            Spacer()
            Circle()
                .fill(red ? .red : .blue)
                .frame(width: 30, height: 30)
                .offset(x: x)
                .animation(.easeInOut(duration: 1)) // Связанные одновременно x и red две зависимости
//                .animation(.easeInOut(duration: 1), value: x)  // Рекомендуется использовать использовать, чтобы связать их отдельно.
//                .animation(.easeInOut(duration: 1), value: red)

            Spacer()
            Button("Animate") {  // 闭包середина改变了две зависимостиизценить
                if x == 0 {
                    x = 100
                } else {
                    x = 0
                }
                red.toggle()
            }
        }
        .frame(width: 500, height: 300)
    }
}

Используя animation<V>(_ animation: Animation?, value: V) Версия,У нас может быть только одно из двух, положение и цвет, для создания плавной анимации. существовать При одновременном изменении нескольких зависимостей,animation(_ animation: Animation?) Очень легко создать ненужную анимацию, и это основная причина, по которой от нее отказались.

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

Язык кода:javascript
копировать
struct Demo2: View {
    @State var x: CGFloat = 0
    @State var red = false
    var body: some View {
        VStack {
            Spacer()

            Circle()
                .fill(red ? .red : .blue)
                .frame(width: 30, height: 30)
                .offset(x: x)
            Spacer()
            Button("Animate") {
                if x == 0 {
                    x = 100
                } else {
                    x = 0
                }
                withAnimation(.easeInOut(duration: 1)) { // Только цвета будут переходить плавно
                    red.toggle()
                }
            }
        }
        .frame(width: 500, height: 300)
    }
}

для разных зависимостей ассоциация отличается от функции временной кривой

Осторожные друзья могут Наружно существовать, как указано выше, когда им нужно работать. временной кривой При создании ассоциаций я использовал словоиспользоватьиз "зависимость" вместо "статус", что потому чтовидизстатус заключается в том, что он имеетиз全部Зависимостииз Общая презентация。witAnimation Позволяет нам использовать один и тот же анимируемый виджет в разных настройках зависимостей. временной кривой。

Язык кода:javascript
копировать
struct Demo4: View {
    @State var x: CGFloat = 0
    @State var y: CGFloat = 0
    var body: some View {
        VStack {
            Spacer()
            Circle()
                .fill(.orange)
                .frame(width: 30, height: 30)
                .offset(x: x, y: y) // x、y Связано с различными функциями временной кривой
            Spacer()
            Button("Animate") {
                withAnimation(.linear) { 
                    if x == 0 { x = 100 } else { x = 0 }
                }
                withAnimation(.easeInOut) {
                    if y == 0 { y = 100 } else { y = 0 }
                }
            }
        }
        .frame(width: 500, height: 500)
    }
}

dual_timing_function_2022-05-04_15.25.59.2022-05-04 15_27_18

потому что offset(x: x, y: y) серединаиз x и y проходить withAnimation Связано с различными функциями временной кривая, поэтому анимация существует в процессе, горизонтальная ось и вертикальная ось в режиме движения отличаются ( x линейна из, y Он медленно входит и выходит из).

в настоящий момент animation<V>(_ animation: Animation?, value: V) Различные ассоциации зависимостей для одного и того же анимируемого виджета пока не поддерживаются. временной кривой

Помимо возможности связывать различные типы функций временной кривойснаружи,SwiftUI Также позволяет функцию ассоциации временной кривой имеет разную продолжительность. Когда один и тот же компонент анимации связан с разными зависимостями с разными функциями продолжительности ( duration Несовместимо или включено repeatForever ),Логика интерполяции и вычислений станет более сложной,Различные комбинации будут иметь разные результаты.,Будьте осторожны при использовании.

Язык кода:javascript
копировать
Button("Animate") {
    withAnimation(.linear) {
        if x == 0 { x = 100 } else { x = 0 }
    }
    withAnimation(.easeInOut(duration: 1.5)) {
        if y == 0 { y = 100 } else { y = 0 }
    }
}

different_duration_2022-05-09_12.44.24.2022-05-09 12_45_01

Используйте withAnimation с осторожностью.

существовать SwiftUI Не предусмотрено animation<V>(_ animation: Animation?, value: V) (иконкретные зависимостиассоциация)модификаторчас,withAnimation по сравнению с animation(_ animation: Animation?) Или Может быть, это лучший выбор, по крайней мере, он может дать понять, что конкретная функция зависимостей временной связанное с электричеством.

Однако, если это не необходимо (например, вам нужно связать разные функции временной кривой), приоритет следует отдавать использованиюиспользовать animation<V>(_ animation: Animation?, value: V) . Это потому что чтохотя withAnimation Зависимости можно указать, но этого не хватает. animation(_ animation: Animation?, value: V) из Размер позиции кода, withAnimation Повлияет на отображение всех зависимостей, связанных с извидом, например, его сложно использовать. withAnimation Реализовать код одиниз Эффект。

Еще одна вещь, на которую следует обратить внимание: использовать withAnimation Когда зависимость должна быть явно включена в замыкание, в противном случае withAnimationВоля不起делатьиспользовать。Например:

Язык кода:javascript
копировать
struct Demo3: View {
    @State var items = (0...3).map { $0 }
    var body: some View {
        VStack {
            Button("In withAnimation") {
                withAnimation(.easeInOut) {
                    items.append(Int.random(in: 0...1000))
                }
            }
            Button("Not in withAnimation") { // делатьиспользовать Array из Расширенный метод
                items.appendWithAnimation(Int.random(in: 0...1000), .easeInOut)
            }
            List {
                ForEach(items, id: \.self) { item in
                    Text("\(item)")
                }
            }
            .frame(width: 500, height: 300)
        }
    }
}

extension Array {
    mutating func appendWithAnimation(_ newElement: Element, _ animation: Animation?) {
        withAnimation(animation) {
            append(newElement)
        }
    }
}

Хотя,существовать Array из Расширенный метод appendWithAnimation используется в withAnimation , но потому что withAnimationиз闭包серединабез有包含идентификацияиз Зависимости,поэтому не активируется Механизм анимации SwiftUI。

Сделайте элементы представления анимационными

Связывание конкретных зависимостей,только что завершенонастраиватьанимация开启条件(конкретные зависимости发生改变)иобозначение插ценить算Закон这一步骤。О том, как получить выгодуиспользовать这些анимация数据(插ценить数据)生成анимация,Это вызваноиконкретные зависимостиассоциацияиз Анимируемые части Решатьиз。

проходитьследовать Animatable соглашение позволяет View или ViewModifier Возможность получения данных анимации ( AnimatableModifier устарел). много SwiftUI из官方часть都уже预先满足了Долженпротокол,Например:offsetframeopacityfill ждать.

Animatable Требования к протоколизму очень просты, достаточно реализовать вычисляемое свойство animatableData

Язык кода:javascript
копировать
public protocol Animatable {

    /// The type defining the data to animate.
    associatedtype AnimatableData : VectorArithmetic

    /// The data to animate.
    var animatableData: Self.AnimatableData { get set }
}

Обратите внимание, что в соглашении указано animatableData этот тип должен удовлетворять VectorArithmetic протокол,Это потому что чтофункция временной может быть удовлетворен только VectorArithmetic протоколизтип Выполнить интерполяционные расчеты。

когда Анимируемые частиассоциацияиз Зависимости Когда происходят изменения, SwiftUI Воляпроходитьобозначениеизфункция временной кривой Выполнить интерполяционные расчеты и постоянно корректировать ассоциации зависимостей с анимированными виджетами. animatableData свойство.

Язык кода:javascript
копировать
struct AnimationDataMonitorView: View, Animatable {
    static var timestamp = Date()
    var number: Double
    var animatableData: Double { // SwiftUI существовать渲染час Обнаружить Долженвиддля Анимируемый, он будет существовать после изменения статуса в соответствии с функцией. временной кривая обеспечивает непрерывную корректировку значений animableData
        get { number }
        set { number = newValue }
    }

    var body: some View {
        let duration = Date().timeIntervalSince(Self.timestamp).formatted(.number.precision(.fractionLength(2)))
        let currentNumber = number.formatted(.number.precision(.fractionLength(2)))
        let _ = print(duration, currentNumber, separator: ",")

        Text(number, format: .number.precision(.fractionLength(3)))
    }
}

struct Demo: View {
    @State var startAnimation = false
    var body: some View {
        VStack {
            AnimationDataMonitorView(number: startAnimation ? 1 : 0) // Объявите форму из в двух состояниях
                .animation(.linear(duration: 0.3), value: startAnimation) // Связанные зависимости временной кривой
            Button("Show Data") {
                AnimationDataMonitorView.timestamp = Date() 
                startAnimation.toggle() // Изменение зависимостей
            }
        }
        .frame(width: 300, height: 300)
    }
}

Приведенный выше код ясно показывает этот процесс.

Процесс декларирования:

  • обозначениефункция временной кривой —— linear
  • Свяжите зависимость startAnimation с линейной
  • AnimationDataMonitorView (можно анимироватьчасть)соответствовать Animatable и полагался на startAnimation

Процесс анимации:

  • Нажмите кнопку, чтобы изменить зависимости startAnimation изценить
  • SwiftUI будет завершено немедленно startAnimation Значение изменяется (в зависимости от изменения значения, которое происходит до начала анимации, например, в данном случае true сразу станет false )
  • SwiftUI Обнаружить AnimationDataMonitorView соответствовать Animatable протокол,делатьиспользовать linear Выполнить интерполяционные расчеты
  • SwiftUI Будет обновляться в соответствии с частотой обновления устройства ( 60 fps/sec или 120 fps/sec)持续делатьиспользовать linear из Результат расчетанастраивать AnimationDataMonitorView из animatableData свойства и AnimationDataMonitorView из body Оценка, рендеринг

В выводе на печать мы видим, как прибывают различные временные узлы из интерполяционных данных:

animatable_data_demo_2022-05-04_17.32.01.2022-05-04 17_34_12

Таблица числовых изменений функции временной кривой, приведенная выше, генерируется этим кодом.

Порекомендуйте несколько представлений Animatable использовать Законизсообщение в блоге:

Advanced SwiftUI Animations – Part 1: Paths[4]

AnimatableModifier in SwiftUI[5]

Если анимируемый элемент имеет несколько изменяемых зависимостей, вам необходимо animatableData установлен на AnimatablePair напечатай так, чтобы SwiftUI Вы можете передавать данные интерполяции анимации, принадлежащие различным зависимостям.

AnimatablePair тип соответствует VectorArithmetic протокол, и его тип упаковки также должен соответствовать цифровому типу. VectorArithmetic протокол

Следующий код демонстрирует AnimatablePair Как сделать использование и как увидеть две разные функции временной кривые интерполированные данные:

Язык кода:javascript
копировать
struct AnimationDataMonitorView: View, Animatable {
    static var timestamp = Date()
    var number1: Double // изменится
    let prefix: String
    var number2: Double // изменится

    var animatableData: AnimatablePair<Double, Double> {
        get { AnimatablePair(number1, number2) }
        set {
            number1 = newValue.first
            number2 = newValue.second
        }
    }

    var body: some View {
        let duration = Date().timeIntervalSince(Self.timestamp).formatted(.number.precision(.fractionLength(2)))
        let currentNumber1 = number1.formatted(.number.precision(.fractionLength(2)))
        let currentNumber2 = number2.formatted(.number.precision(.fractionLength(2)))
        let _ = print(duration, currentNumber1, currentNumber2, separator: ",")

        HStack {
            Text(prefix)
                .foregroundColor(.green)
            Text(number1, format: .number.precision(.fractionLength(3)))
                .foregroundColor(.red)
            Text(number2, format: .number.precision(.fractionLength(3)))
                .foregroundColor(.blue)
        }
    }
}

struct Demo: View {
    @State var startNumber1 = false
    @State var startNumber2 = false
    var body: some View {
        VStack {
            AnimationDataMonitorView(
                number1: startNumber1 ? 1 : 0,
                prefix: "Hi:",
                number2: startNumber2 ? 1 : 0
            )
            Button("Animate") {
                AnimationDataMonitorView.timestamp = Date()
                withAnimation(.linear) {
                    startNumber1.toggle()
                }
                withAnimation(.easeInOut) {
                    startNumber2.toggle()
                }
            }
        }
        .frame(width: 300, height: 300)
    }
}

animatable_dual_data_demo_2022-05-04_18.17.39.2022-05-04 18_18_51

SwiftUI существование очень умно работает при передаче интерполированных данных и меняет только зависимости animatableData Передать Давать анимационные элементы. Например, в приведенном выше коде параметры prefix не меняется, поэтому существование синтезируется AnimatablePair Данные будут автоматически пропущены и будут только синтезированы number1 и number2

Если вам нужно передать больше параметров, вы можете вложить их, используя тип AnimatablePair, например:

Язык кода:javascript
копировать
AnimatablePair<CGFloat, AnimatablePair<Float, AnimatablePair<Double, CGFloat>>>
// newValue.second.second.first.

Используйте транзакцию для более точного контроля.

использовать SwiftUI изофициальный язык для описания【Свяжите функции временной кривой с состояниямииз Процесс] должно быть: длявид Декларация транзакции ( Transaction)。Транзакции обеспечивают большую гибкостьизфункция кривойтипнастраивать方式以及анимация开关и临час状态标识。

Любой модификатор animation Или глобальная функция withAnimation , фактически указаны в существованиивид Transaction из метода ярлыка, который внутренне соответствует transaction и withTransaction

например,withAnimation Фактически, соответствующее из:

Язык кода:javascript
копировать
withAnimation(.easeInOut){
    show.toggle()
}
// Соответствует для
let transaction = Transaction(animation: .easeInOut)
withTransaction(transaction) {
    show.toggle()
}

animation(_ animation: Animation?) То же самоепроходить Transaction Для достижения из:

Язык кода:javascript
копировать
// Код взят из swiftinterface
extension SwiftUI.View {
    @_disfavoredOverload @inlinable public func animation(_ animation: SwiftUI.Animation?) -> some SwiftUI.View {
        return transaction { t in
            if !t.disablesAnimations {
                t.animation = animation
            }
        }
    }
}

Transaction Предоставить из disablesAnimations и isContinuous Это может помочь разработчикам лучше управлять анимацией, например:

  • Для динамического выбора требуется Связанная функция временной кривой
Язык кода:javascript
копировать
Text("Hi")
    .offset(x: animated ? 100 : 0)
    .transaction {
        if position < 0 || position > 100 {
            $0.animation = .easeInOut
        } else {
            $0.animation = .linear
        }
    }

transaction издляиспользовать область действия и ассоциации из зависимостей и не указывать конкретные зависимости Версияиз animation Это то же самоеиз,Он не имеет возможности связывать конкретные зависимости

Язык кода:javascript
копировать
// не означает только x Ассоциация: если в пределах области домена изменяются другие зависимости, анимация также будет создана.
.transaction {
    if x == 0 {
        $0.animation = .linear
    } else {
        $0.animation = nil
    }
}

// Эквивалентно
.animation(x == 0 ? .linear : nil)
  • disablesAnimations
Язык кода:javascript
копировать
struct Demo: View {
    @State var position: CGFloat = 40
    var body: some View {
        VStack {
            Text("Hi")
                .offset(x: position, y: position)
                .animation(.easeInOut, value: position)

            Slider(value: $position, in: 0...150)
            Button("Animate") {
                var transaction = Transaction() // Не указана функция временной кривая, сохранит первоначальные настройки (в данном случае для easeInOut)
                if position < 100 { transaction.disablesAnimations = true }
                withTransaction(transaction) { // withTransaction Может запрещать оригинальные транзакции из функции временной кривой(由 animation связанный), но не может быть заблокирован transaction Связанная функция временной кривой
                    position = 0
                }
            }
        }
        .frame(width: 400, height: 500)
    }
}

withTransaction (проходитьнастраивать disablesAnimations заблокировать анимацию) + animation<V>(_ animation: Animation?, value: V) Это более зрелый матч.

  • isContinuous
Язык кода:javascript
копировать
struct Demo: View {
    @GestureState var position: CGPoint = .zero
    var body: some View {
        VStack {
            Circle()
                .fill(.orange)
                .frame(width: 30, height: 50)
                .offset(x: position.x, y: position.y)
                .transaction {
                    if $0.isContinuous {
                        $0.animation = nil // При перетаскивании не настраиватьфункцию временной кривой
                    } else {
                        $0.animation = .easeInOut(duration: 1)
                    }
                }
                .gesture(
                    DragGesture()
                        .updating($position, body: { current, state, transaction in
                            state = .init(x: current.translation.width, y: current.translation.height)
                            transaction.isContinuous = true // При перетаскивании настраивается логотип
                        })
                )
        }
        .frame(width: 400, height: 500)
    }
}

isContinuous_2022-05-06 11.26.20.2022-05-06 11_27_42

Слайдер При перетаскивании существование будет автоматически настраиваться как Непрерывное, но фактическое измерение не соответствует описанию. Но мы можем сами существовать в коде в 利использовать его настройку временного статуса.

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

  • UIViewRepresentableContext
  • AsyncImage
  • GestureState
  • Биндинг и др.

Например, установите транзакцию для привязки:

Язык кода:javascript
копировать
struct Demo: View {
    @State var animated = false
    let animation: Animation?

    var animatedBinding: Binding<Bool> { // Генерировать содержит указанные Transaction из Binding тип
        let transaction = Transaction(animation: animation)
        return $animated.transaction(transaction)
    }

    var body: some View {
        VStack {
            Text("Hi")
                .offset(x: animated ? 100 : 0)

            Toggle("Animated", isOn: animatedBinding) // Анимационные эффекты будут автоматически возникать при нажатии
        }
        .frame(width: 400, height: 500)
    }
}

PlaygroundPage.current.setLiveView(Demo(animation: .easeInOut))

binding_transaction_2022-05-06_11.33.10.2022-05-06 11_34_38

Дополнительные примечания о том, как функции временной кривой связаны с состояниями.

  • SwiftUI Делает только самую близкую позицию используемой анимируемой детали из связанной (функция временной кривая зависимость) утверждение.
Язык кода:javascript
копировать
Circle()
    .fill(red ? .red : .blue)
    .animation(.easeInOut(duration: 1),value: red)  // делатьиспользоватьэто    .animation(.linear(duration: 3), value: red)

  • withAnimation( withTransaction )обозначениеизфункция временной кривую нельзя изменить animation средняя корреляционная функция
Язык кода:javascript
копировать
Circle()
    .fill(red ? .red : .blue)
    .animation(.easeInOut(duration: 1), value: red)  // делатьиспользоватьэтоButton("Change red"){
    withAnimation(.linear(duration:3)){  // Рабочая область самая большая, что означает максимальное расстояние от анимированного компонента.
        red.toggle()
    }
}
  • animation и withAnimation Стоит выбрать одно из двух
  • withTransaction Можно заблокировать animation Связанная функция временной кривой проходитьнастраивать disablesAnimations Можно запретить использовать оригинальную функцию в делах временной кривая (нельзя изменить), подробности см. в предыдущем разделе
  • Примите соответствующий подход издинамической настройки функции временной кривой.
Язык кода:javascript
копировать
// Первый метод и конкретная ассоциация зависимостей более подходят, когда есть только две ситуации.
.animation(red ? .linear : .easeIn , value: red) 

// Способ второй, Может обрабатывать больше логики, но не конкретные зависимости.
.transaction{
    switch status{
        case .one:
            $0.animation = .linear
        case .two:
            $0.animation = .easeIn
        case .three:
            $0.animation = nil
    }
}

// Метод 3 поддерживает сложную логику и связан с конкретными состояниями.
var animation:Animation?{
    // Даже если в замыкании есть несколько разных зависимостей, это не повлияет animation Только указывает, что из зависит от атрибута связанныйиз.
    switch status{
        case .one:
            $0.animation = .linear
        case .two:
            $0.animation = .easeIn
        case .three:
            $0.animation = nil
    }
}

.animation(animation , value: status)

// Способ 4, объем работ большой
var animation:Animation?{
    switch status{
        case .one:
            $0.animation = .linear
        case .two:
            $0.animation = .easeIn
        case .three:
            $0.animation = nil
    }
}

withAnimation(animation){
    ...
}

// Способ пятый, объем работ большой
var animation:Animation?{
    switch status{
        case .one:
            $0.animation = .linear
        case .two:
            $0.animation = .easeIn
        case .three:
            $0.animation = nil
    }
}
var transaction = Transaction(animation:animation)
withTransaction(transaction){
    ...
}

// ждатьждать

Переход

Что такое переход

SwiftUI из переходатип( AnyTransition ) для Анимируемых частиизпереупаковка。когда状态из Изменение приводит квиддеревоиз веткиКогда происходят изменения, SwiftUI Воляделатьиспользоватьего пакетиз Анимируемые частиверновид进ХОРОШОанимация处理。

Чтобы осуществить переход, вам также необходимо соответствовать трем элементам анимации SwiftUI.

Язык кода:javascript
копировать
struct TransitionDemo: View {
    @State var show = true
    var body: some View {
        VStack {
            Spacer()
            Text("Hello")
            if show {
                Text("World")
                    .transition(.slide) // Оживляемые части (среди них существует упаковка)
            }
            Spacer()
            Button(show ? "Hide" : "Show") {
                show.toggle() 
            }
        }
        .animation(.easeInOut(duration:3), value: show) // Создайте связанные зависимости и установите функцию временной кривой
        .frame(width: 300, height: 300)
    }
}

Следовательно, То же, что и всеиз SwiftUI Как и анимированные элементы, переходы также поддерживают прерываемую анимацию. Например, когда выполняется анимация появления существующего объекта, измените статус show Вернуться к true ,SwiftUI сохранит текущее состояние ветки (не будет воссоздавать его),См. пример, прикрепленный к этой статье).

Пользовательский переход

существовать SwiftUI середина实现Пользовательский переход не сложный, если только вам не нужно создавать крутые визуальные эффекты, что возможно в большинстве случаев. SwiftUI уже Предоставить из Анимируемые частикомбинированный。

Язык кода:javascript
копировать
struct MyTransition: ViewModifier { // Пользовательский переходиз包装верно象要求соответствовать ViewModifier протокол
    let rotation: Angle
    func body(content: Content) -> some View {
        content
            .rotationEffect(rotation) // Анимируемые части
    }
}

extension AnyTransition {
    static var rotation: AnyTransition {
        AnyTransition.modifier(
            active: MyTransition(rotation: .degrees(360)),
            identity: MyTransition(rotation: .zero)
        )
    }
}

struct CustomTransitionDemo: View {
    @State var show = true
    var body: some View {
        VStack {
            VStack {
                Spacer()
                Text("Hello")
                if show {
                    Text("World")
                        .transition(.rotation.combined(with: .opacity))
                }
                Spacer()
            }
            .animation(.easeInOut(duration: 2), value: show) // существовать Заявление здесь,Button из текста не будет иметь эффекта анимации
            Button(show ? "Hide" : "Show") {
                show.toggle()
            }
        }
//        .animation(.easeInOut(duration: 2), value: show) // Если здесь указано существование, то верно Button из текста также имеет влияние, результат такой, как показано ниже
        .frame(width: 300, height: 300)
        .onChange(of: show) {
            print($0)
        }
    }
}

custom_transition_2022-05-04_19.55.51.2022-05-04 19_56_55

Хотя MyTransition На первый взгляд не совпадает Animatable протокол, но откуда rotationEffect (можно анимировать ViewModifier ) помог нам добиться эффекта анимации.

кроме того,我们也可以делатьиспользоватьсоответствовать Animatable из GeometryEffect( соответствовать ViewModifier и Animatable ) для создания сложных перехода Эффект。

Более крутоиз перехода定制方Закон请阅读 Javier из Статей Advanced SwiftUI Transitions[6]

Состояние, идентификация вида, анимация

Теперь SwiftUI из анимации предназначен для создания плавного перехода из одного состояния в другое, тогда мы должны иметь правильное понимание состояния (зависимости) от изменений, которые могут привести к таким результатам.

SwiftUI Существует два способа определения виддиспользовать: структурная идентификацияи Явная идентификация. В анимации разные методы маркировки требуют разных точек внимания.

структурная идентификация

Хотя в следующих двух фрагментах кода используются структурные теги вида ( 以Местосуществоватьизвидуровень位置итип进ХОРОШО标识 ), но намерения у них совершенно разные.

Язык кода:javascript
копировать
// код один
if show {
    Text("Hello")  // Филиал один
} else {
    Text("Hello")  // Филиал второй
      .offset(y : 100)
}

// Код два
Text("Hello")
    .offset(y : show ? 100 : 0)  // Два статуса для одного и того же вида

код одинописалсуществовать Зависимости show Когда происходят изменения, SwiftUI Волясуществовать Филиал одини Филиал второйсередина进ХОРОШО切换。В этом случае,Мы можем применить transition Установить Филиал отдельно одини Филиал второйиз进出场анимация( Вы также можете выбрать ветку и установить ее равномерно снаружи. Transition ),但无Закон要求Филиал одиндвигатьсяприезжать Филиал второйвыше。

Язык кода:javascript
копировать
// код один
VStack{  //  Контейнер макета makeuse
    if !show {
        Text("Hello")  // Филиал один
           .transition(.scale)
    } else {
        Text("Hello")  // Филиал второй
          .offset(y : 100)
          .transition(.move(edge: .bottom))
    }
}
.animation(.easeIn, value: show)

status_for_transition_2022-05-09_15.11.26.2022-05-09 15_12_10

В приведенном выше коде есть две вещи, на которые следует обратить внимание:

  • Должно существовать условное суждение вне использованияиспользовать animation ,потому чтотолькосуществовать if - else оператор изOutside, только для использования домена show решение действительно
  • отвечать Контейнер макета makeuse( VStack、ZStack、HStack вид ) оборачивает оператор условного суждения ( 不要делатьиспользовать Group ). Поскольку две ветки видсуществовать появятся одновременно, только существующий контейнер макета будет правильно обрабатывать анимацию перехода. Группа 只能верно其子元素进ХОРОШО统一настраивать,Не оборудован для работы с обеими ветвямивид同час出现из Состояние(будет одинвидветвьиз переходапотерянный)。

Код дваописалсуществовать show Когда происходят изменения, один и тот же видиз находится в разных состояниях ( offset из y значения разные). Следовательно, по сравнению с существующей функцией временной После привязки кривой статус вида изменится с первого ( y : 0 ) из позиции движения приехать в штат два ( y : 100) из положения.

Язык кода:javascript
копировать
// Код два
Text("Hello")
    .offset(y : show ? 100 : 0)  // Два статуса для одного и того же вида
    .animation(.spring(), value: show)

status_offset_2022-05-09_15.14.12.2022-05-09 15_14_45

О видизструктурной Содержание идентификации можно найти в ViewBuilder Исследования (Часть 2) —— Учитесь на подражании[7]

Явная идентификация

существовать SwiftUI В языке существует два способа установить явную идентификацию представлений: ForEach. и id модификатор.

  • Давать ForEach Обеспечить стабильный и уникальный изиз KeyPath Помечено как для.
Язык кода:javascript
копировать
struct Demo: View {
    @State var items = (0...100).map { $0 }
    var body: some View {
        VStack {
            List {
                ForEach(items,id: \.self) { item in // id: \.self делатьиспользовать element делатьдля identifier
                    Text("\(item)")
                }
            }
            .animation(.easeInOut, value: items)
            Button("Remove Second") {
                items.remove(at: 1)
            }
            Button("add Second") {  // существовать items В них появятся одни и те же элементы, разрушающие уникальность логотипа.
                items.insert(Int.random(in: 0...100), at: 1)
            }
        }
        .frame(width: 400, height: 500)
    }
}

items представляет собой массив целых чисел. Выше из кода используется в \.self Основание для идентификации. Это означает, что когда в массиве появляются два одинаковых элемента (нажмите кнопку «Добавить»), SwiftUI не сможет правильно определить наши намерения —— С каким элементом вы хотите работать (то же значение означает тот же идентификатор). Таким образом, существует высокая вероятность того, что что неправильно распознает видиз, что приводит к аномалиям анимации. В анимации ниже, когда появляется тот же элемент When, SwiftUI Предупреждение вынесено.

foreach_id_error_2022-05-09_16.41.18.2022-05-09 16_43_22

для ForEach Оснащен уникальными идентификаториз Источники данных могут эффективно избежатьпоэтомуи производитьизанимация异常。

Язык кода:javascript
копировать
struct Item: Identifiable, Equatable {
    let id = UUID() // уникальный идентификатор
    let number: Int
}

struct Demo: View {
    @State var items = (0...100).map { Item(number: $0) }
    var body: some View {
        VStack {
            List {  // в настоящий момент无Закондля List внутрииз item обозначение transition , еще один, не очень совместимый с оригинальным управлением SwiftUI Анимация из примера. Заменить на ScrollView Может поддерживать изображение item из перехода
                ForEach(items, id: \.id) { item in
                    Text("\(item.number)")
                }
            }
            .animation(.easeInOut, value: items) // List Вместо этого используйте эту ассоциацию для обработки анимации. ForEach
            Button("Remove Second") {
                items.remove(at: 1)
            }
            Button("add Second") {
                items.insert(Item(number: Int.random(in: 0...100)), at: 1)
            }
        }
        .frame(width: 400, height: 500)
    }
}
  • Идентификатор модификатора необходимо использовать для перехода

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

Язык кода:javascript
копировать
struct Demo: View {
    @State var id = UUID()
    var body: some View {
        VStack {
            Spacer()
            Text("Hello \(UUID().uuidString)")
                .id(id) // id когда происходят изменения Исходный вид удаляется и вставляется новый вид.
                .transition(.slide) 
                .animation(.easeOut, value: id)
            Button("Update id") {
                id = UUID()
            }
            Spacer()
        }
        .frame(width: 300, height: 300)
    }
}

id_transition_2022-05-09_16.58.42.2022-05-09 16_59_17

SwiftUI в настоящий моментсуществовать处理因 id ценить变化и производитьизвид Конвертироватьиз Эффект一般,Поддерживает только некоторые из перехода Эффект。

Информацию о явной идентификации см. оптимизациясуществовать SwiftUI List Отображение большого набора данных об эффективности реагирования [8] одно предложение

Сожаления и перспективы

Теоретически, как только вы освоите Механизм анимации Благодаря SwiftUI вы сможете легко управлять кодом и свободно управлять анимацией. Но реальность жестока. потому что SwiftUI Это молодой фреймворк, и многие базовые реализации все еще полагаются на другие фреймворки. API из Пакет,поэтому Во многих ситуацияхизделатьиспользовать Опыт все еще полон фрагментации。

Проблема с управляющей анимацией

SwiftUI Многие элементы управления в Китае изготовлены из использованных материалов. UIKit( AppKit ) элемент управления инкапсулирован для реализации, а текущая обработка анимации отсутствует.

существовать ViewBuilder Исследования (Часть 2) —— Учитесь на подражании[9] одно В предложении мы показываем SwiftUI из Text 是如何处理этоиз Расширенный методиз。хотя UIViewRepresentableContext Базовые элементы управления уже предоставляют необходимые элементы управления анимацией. Transaction информация, но в настоящее время SwiftUI Официальные органы управления на это не отреагировали. Например, следующий код не может обеспечить плавный переход.

Язык кода:javascript
копировать
Text("Hello world")
    .foregroundColor(animated ? .red : .blue) // на основе Инкапсуляция UIKit (AppKit) элементов управления и расширений практически невозможна для достижения управления анимацией
    .font(animated ? .callout : .title3)

Хотя мы можем предложить некоторые способы решения этих проблем.,Но это не только увеличит рабочую нагрузку,При этом часть производительности будет потеряна.

Paul Hudson существовать How to animate the size of text[10] одно предложение демонстрирует, как создать плавную анимацию перехода между размерами и размерами шрифта.

Следующий код может помочь Text добиться плавного перехода цвета текста.

Язык кода:javascript
копировать
extension View {
    func animatableForeground(_ color: Color) -> some View {
        self
            .overlay(Rectangle().fill(color))
            .mask {
                self
                    .blendMode(.overlay)
            }
    }
}

struct Demo: View {
    @State var animated = false
    var body: some View {
        VStack {
            Button("Animate") {
                animated.toggle()
            }
            Text("Hello world")
                .font(.title)
                .animatableForeground(animated ? .green : .orange)
                .animation(.easeInOut(duration: 1), value: animated)
        }
    }
}

animatable_color_of_text_2022-05-05_14.35.19.2022-05-05 14_36_15

Проблемы с анимацией контроллера

по сравнению с Управление анимацией, Проблемы с анимацией контроллера решить еще сложнее. Навигациявиев, табвиев, лист Компонент ожидания вообще нельзя найти в собственном решении для управления анимацией, даже если его скорректировать. UIKit( AppKit ) Код может вносить лишь незначительные изменения в анимацию (например, управлять включением анимации). Средства и эффекты равны SwiftUI Существует огромный пробел в возможностях встроенной анимации.

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

Проблемы с производительностью анимации

Почти неизбежно, что адаптивная анимация будет немного менее отзывчивой, чем императивная. SwiftUI существуют Некоторые усилия были предприняты для оптимизации производительности анимации (например: Canvas, DrawingGroup ). Я надеюсь, что по мере дальнейшей оптимизации кода и совершенствования оборудования этот разрыв будет становиться все меньше и меньше.

Подвести итог

  • Анимация создана для создания плавного перехода из одного состояния прибытия в другое состояние.
  • Для объявления анимации требуется три элемента
  • Поймите, к каким результатам может привести изменение статуса — один и тот же видиз в разных штатах или разные ветки извидов.
  • Функция временной кривой Чем точнее корреляция, тем меньше вероятность возникновения аномальной анимации.
  • Уникальная и стабильная идентификация извидов (либо структурная идентификациявсе еще Явная идентификация) помогает избежать аномалий анимации.

Механизм анимации Дизайн SwiftUI по-прежнему на высоте.,Я считаю, что по мере того, как степень завершенности продолжает улучшаться,Разработчики могут добиться лучших интерактивных эффектов с меньшим количеством кода.

Надеюсь, эта статья может быть вам полезна. Также приветствуем вас Twitter[11]Дискорд-канал[12]или Блог нижеиздоска объявленийи我进ХОРОШО交流。

Ссылки

[1] www.fatbobman.com: https://www.fatbobman.com

[2] Получите все коды для этой статьи здесь: https://github.com/fatbobman/BlogCodes/tree/main/Animation

[3] The magic of Animatable values in SwiftUI: https://swiftwithmajid.com/2020/06/17/the-magic-of-animatable-values-in-swiftui/

[4] Advanced SwiftUI Animations – Part 1: Paths: https://swiftui-lab.com/swiftui-animations-part1/

[5] AnimatableModifier in SwiftUI: https://swiftwithmajid.com/2021/01/11/animatablemodifier-in-swiftui/

[6] Advanced SwiftUI Transitions: https://swiftui-lab.com/advanced-transitions/

[7] ViewBuilder Исследования (Часть 2) —— Учитесь на подражании: https://www.fatbobman.com/posts/viewBuilder2/

[8] оптимизациясуществовать SwiftUI List Отображение большого набора данных по эффективности ответа: https://www.fatbobman.com/posts/optimize_the_response_efficiency_of_List/

[9] ViewBuilder Исследования (Часть 2) —— Учитесь на подражании: https://www.fatbobman.com/posts/viewBuilder2/

[10] How to animate the size of text: https://www.hackingwithswift.com/quick-start/swiftui/how-to-animate-the-size-of-text

[11] Twitter: https://twitter.com/fatbobman

[12] Discord Канал: https://discord.gg/ApqXmy5pQJ

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose