Цепная анимация в SwiftUI

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

Допустим, у меня есть представление, которое сначала нужно масштабировать, затем подождать несколько секунд, а затем исчезнуть (а затем подождать пару секунд и начать заново - до бесконечности).

Если я попытаюсь использовать несколько блоков withAnimation() в одном представлении / стеке, они в конечном итоге будут мешать друг другу и портить анимацию.

Лучшее, что я мог придумать, это вызвать пользовательскую функцию в модификаторе.onAppear() начальных представлений и в этой функции иметь блоки withAnimation() для каждого этапа анимации с задержками между ними. Итак, в основном это выглядит примерно так:

func doAnimations() {
  withAnimation(...)

  DispatchQueue.main.asyncAfter(...)
    withAnimation(...)

  DispatchQueue.main.asyncAfter(...)
    withAnimation(...)

  ...

}

В итоге получается довольно долго и не очень "красиво". Я уверен, что должен быть лучший / более хороший способ сделать это, но все, что я пробовал до сих пор, не дало мне точный поток, который я хочу.

Любые идеи / рекомендации / советы будут высоко оценены. Спасибо!

1 ответ

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

withAnimation(Animation.easeIn(duration: 1.23)) {
    self.doSomethingFirst()
}

withAnimation(Animation.easeOut(duration: 4.56).delay(1.23)) {
    self.thenDoSomethingElse()
}

withAnimation(Animation.default.delay(1.23 + 4.56)) {
    self.andThenDoAThirdThing()
}

Я обнаружил, что это приводит к более гладкой цепной анимации, чем при использовании DispatchQueue или Timer, возможно, потому, что он использует один и тот же планировщик для всех анимаций.

Жонглирование всеми задержками и продолжительностью может быть проблемой, поэтому амбициозный разработчик может абстрагировать вычисления в какой-то глобальный withChainedAnimation функция, чем делает это за вас.

Использование таймера работает. Это из моего собственного проекта:

@State private var isShowing = true
@State private var timer: Timer?

...

func askQuestion() {
    withAnimation(Animation.easeInOut(duration: 1).delay(0.5)) {
        isShowing.toggle()
    }
    timer = Timer.scheduledTimer(withTimeInterval: 1.6, repeats: false) { _ in
        withAnimation(.easeInOut(duration: 1)) {
            self.isShowing.toggle()
        }
        self.timer?.invalidate()
    }

    // code here executes before the timer is triggered.

}

Боюсь, на данный момент нет поддержки для чего-то вроде ключевых кадров. По крайней мере, они могли бы добавить onAnimationEnd()... но такого нет.

Там, где мне удалось немного повезти, - анимация траекторий. Хотя ключевых кадров нет, у вас больше контроля, так как вы можете определить свои "AnimatableData". Например, проверьте мой ответ на другой вопрос: /questions/49548685/kak-ozhivit-put-v-swiftui/49548694#49548694

В этом случае, это в основном дуга, которая вращается, но увеличивается от нуля до некоторой длины, и в конце поворота она постепенно возвращается к нулевой длине. Анимация имеет 3 фазы: сначала один конец дуги движется, а другой - нет. Затем они оба движутся вместе с одинаковой скоростью, и, наконец, второй конец достигает первого. Мой первый подход состоял в том, чтобы использовать идею DispatchQueue, и она работала, но я согласен: это ужасно безобразно. Затем я понимаю, как правильно использовать AnimatableData. Так что... если вы оживляете пути, вам повезло. В противном случае, похоже, нам придется ждать возможности более элегантного кода.

Другие вопросы по тегам