web-dev-qa-db-fra.com

Chaînage d'animations dans SwiftUI

Je travaille sur une animation relativement complexe dans SwiftUI et je me demande quelle est la meilleure façon/la plus élégante de chaîner les différentes phases d'animation.

Disons que j'ai une vue qui doit d'abord être mise à l'échelle, puis attendre quelques secondes, puis s'estomper (puis attendre quelques secondes et recommencer - indéfiniment).

Si j'essaie d'utiliser plusieurs blocs withAnimation () sur la même vue/pile, ils finissent par interférer les uns avec les autres et gâcher l'animation.

Le mieux que j'ai pu trouver jusqu'à présent est d'appeler une fonction personnalisée sur le modificateur de vues initiales .onAppear () et dans cette fonction, d'avoir des blocs withAnimation () pour chaque étape de l'animation avec des retards entre eux. Donc, cela ressemble essentiellement à ceci:

func doAnimations() {
  withAnimation(...)

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

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

  ...

}

Cela finit par être assez long et pas très "joli". Je suis sûr qu'il doit y avoir un moyen meilleur/plus agréable de le faire, mais tout ce que j'ai essayé jusqu'à présent ne m'a pas donné le flux exact que je veux.

Toute idée/recommandation/astuce serait très appréciée. Merci!

8
Rony Rozen

Comme mentionné dans les autres réponses, il n'y a actuellement aucun mécanisme de chaînage des animations dans SwiftUI, mais vous n'avez pas nécessairement besoin d'utiliser un minuteur manuel. À la place, vous pouvez utiliser la fonction delay sur l'animation chaînée:

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

J'ai trouvé que cela entraînait des animations chaînées plus cohérentes que l'utilisation d'un DispatchQueue ou Timer, peut-être parce qu'il utilise le même planificateur pour toutes les animations.

Jongler avec tous les retards et durées peut être un problème, donc un développeur ambitieux peut résumer les calculs dans une fonction globale withChainedAnimation qui la gère pour vous.

3
marcprux

L'utilisation d'une minuterie fonctionne. Ceci de mon propre projet:

@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.

}
2
Hugo F

Je crains, pour le moment, qu'il n'y ait pas de support pour quelque chose comme des images clés. Au moins, ils auraient pu ajouter une onAnimationEnd()... mais il n'y a rien de tel.

Là où j'ai réussi à avoir de la chance, c'est l'animation de chemins de forme. Bien qu'il n'y ait pas d'images clés, vous avez plus de contrôle, car vous pouvez définir vos "AnimatableData". Pour un exemple, consultez ma réponse à une autre question: https://stackoverflow.com/a/56885066/7786555

Dans ce cas, il s'agit essentiellement d'un arc qui tourne, mais passe de zéro à une certaine longueur et à la fin du tour, il revient progressivement à zéro. L'animation comporte 3 phases: au début, une extrémité de l'arc se déplace, mais pas l'autre. Ensuite, ils se déplacent tous les deux à la même vitesse et enfin la deuxième extrémité atteint la première. Ma première approche a été d'utiliser l'idée de DispatchQueue, et cela a fonctionné, mais je suis d'accord: c'est terriblement moche. J'ai ensuite compris comment utiliser correctement AnimatableData. Donc ... si vous animez des chemins, vous avez de la chance. Sinon, il semble que nous devrons attendre la possibilité d'un code plus élégant.

0
kontiki