Créer des animations est super. Ils sont une partie importante
des directives d'interface humaine iOS . Les animations aident Ă attirer l'attention de l'utilisateur sur des choses importantes ou rendent simplement l'application moins ennuyeuse.
Il existe plusieurs façons d'implémenter une animation dans iOS. La façon la plus populaire est
probablement d' utiliser
UIView.animate (withDuration: animations :) . Vous pouvez animer un calque d'image Ă l'aide de
CABasicAnimation . De plus, UIKit vous permet de configurer des animations personnalisées pour afficher le contrôleur à l'aide de
UIViewControllerTransitioningDelegate .
Dans cet article, je veux discuter d'une autre façon passionnante d'animer des vues -
UIViewPropertyAnimator . Cette classe fournit beaucoup plus de fonctions de gestion que son prédécesseur,
UIView.animat e. Utilisez-le pour créer des animations temporaires, interactives et interrompues. De plus, il est possible de changer rapidement d'animateur.
Présentation de UIViewPropertyAnimator
UIViewPropertyAnimator a été introduit dans
iOS 10 . Il vous permet de créer des animations de manière orientée objet. Regardons un exemple d'animation créée à l'aide de
UIViewPropertyAnimator .

Voici comment c'était lors de l'utilisation d'UIView.
UIView.animate(withDuration: 0.3) { view.frame = view.frame.offsetBy(dx: 100, dy: 0) }
Et voici comment le faire avec
UIViewPropertyAnimator :
let animator = UIViewPropertyAnimator(duration:0.3, curve: .linear) { view.frame = view.frame.offsetBy(dx:100, dy:0) } animator.startAnimation()
Si vous devez vérifier l'animation, créez simplement un Playground et exécutez le code comme indiqué ci-dessous. Les deux fragments de code conduiront au même résultat.

Vous pourriez penser que dans cet exemple, il n'y a pas beaucoup de différence. Alors, quel est l'intérêt d'ajouter une nouvelle façon de créer des animations?
UIViewPropertyAnimator devient plus utile lorsque vous devez créer des animations interactives.
Animation interactive et interrompue
Vous souvenez-vous du geste classique «Glissement de doigt pour déverrouiller l'appareil»? Ou le geste "Déplacez votre doigt sur l'écran de bas en haut" pour ouvrir le Control Center? Ce sont d'excellents exemples d'animation interactive. Vous pouvez commencer à déplacer l'image avec votre doigt, puis la relâcher et l'image reviendra à sa position d'origine. De plus, vous pouvez capturer l'image pendant l'animation et continuer à la déplacer avec votre doigt.
Les animations UIView ne permettent pas de contrôler facilement le pourcentage d'achèvement de l'animation. Vous ne pouvez pas suspendre l'animation au milieu d'une boucle et continuer son exécution après une interruption.
Dans ce cas, nous parlerons d'
UIViewPropertyAnimator . Ensuite, nous verrons comment vous pouvez facilement créer une animation entièrement interactive et interrompue et une animation inverse en quelques étapes.
Préparation du projet de lancement
Vous devez d'abord
télécharger le projet de démarrage .
Une fois que vous avez ouvert l'archive, vous trouverez l'application
CityGuide qui aide les utilisateurs à planifier leurs vacances. L'utilisateur peut faire défiler la liste des villes, puis ouvrir une description détaillée avec des informations détaillées sur la ville qu'il a aimé.
Considérez le code source du projet avant de commencer à créer de belles animations. Voici ce que vous pouvez trouver dans un projet en l'ouvrant dans
Xcode :
- ViewController.swift : contrĂ´leur d'application principal avec UICollectionView qui affiche un tableau d'objets City .
- CityCollectionViewCell.swift: cellule pour afficher la ville . En fait, dans cet article, la plupart des modifications s'appliqueront à cette classe. Vous pouvez remarquer que descriptionLabel et closeButton sont déjà définis dans la classe. Cependant, après le démarrage de l'application, ces objets seront masqués. Ne vous inquiétez pas, ils seront visibles un peu plus tard. Cette classe a également des propriétés collectionView et index . Plus tard, ils seront utilisés pour l'animation.
- CityCollectionViewFlowLayout.swift: Cette classe est responsable du défilement horizontal. Nous ne le changerons pas encore.
- City.swift : Le modèle d'application principal possède une méthode utilisée dans ViewController.
- Main.storyboard: Vous y trouverez l'interface utilisateur pour ViewController et CityCollectionViewCell .
Essayons de créer et d'exécuter l'exemple d'application. À la suite de cela, nous obtenons ce qui suit.
Implémentation d'une animation de déploiement et de repli
Après avoir démarré l'application, une liste de villes s'affiche. Mais l'utilisateur ne peut pas interagir avec des objets sous forme de cellules. Vous devez maintenant afficher les informations de chaque ville lorsque l'utilisateur clique sur l'une des cellules. Jetez un œil à la version finale de l'application. Voici ce qui devait réellement être développé:
L'animation est bonne, non? Mais il n'y a rien de spécial ici, c'est juste la logique de base de
UIViewPropertyAnimator . Voyons comment implémenter ce type d'animation. Créez une méthode
collectionView (_: didSelectItemAt) , ajoutez le fragment de code suivant Ă la fin du fichier
ViewController :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let selectedCell = collectionView.cellForItem(at: indexPath)! as! CityCollectionViewCell selectedCell.toggle() }
Maintenant, nous devons implémenter la méthode
bascule . Passons Ă
CityCollectionViewCell.swift et implémentons cette méthode.
Tout d'abord, ajoutez l'énumération
State en haut du fichier, juste avant la déclaration de la classe
CityCollectionViewCell . Cette liste vous permet de suivre l'état d'une cellule:
private enum State { case expanded case collapsed var change: State { switch self { case .expanded: return .collapsed case .collapsed: return .expanded } } }
Ajoutez des propriétés pour contrôler l'animation dans la classe
CityCollectionViewCell :
private var initialFrame: CGRect? private var state: State = .collapsed private lazy var animator: UIViewPropertyAnimator = { return UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) }()
La variable
initialFrame est utilisée pour stocker l'image de cellule jusqu'à l'
exécution de l'animation.
état est utilisé pour suivre si la cellule est développée ou réduite. Et la variable
animateur est utilisée pour contrôler l'animation.
Ajoutez maintenant la méthode
bascule et appelez-la à partir de la méthode
close , par exemple:
@IBAction func close(_ sender: Any) { toggle() } func toggle() { switch state { case .expanded: collapse() case .collapsed: expand() } }
Ensuite, nous ajoutons deux autres méthodes:
expand () et
collapse () . Nous poursuivrons leur mise en œuvre. Tout d'abord, nous commençons avec la méthode
expansiond () :
private func expand() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.initialFrame = self.frame self.descriptionLabel.alpha = 1 self.closeButton.alpha = 1 self.layer.cornerRadius = 0 self.frame = CGRect(x: collectionView.contentOffset.x, y:0, width: collectionView.frame.width, height: collectionView.frame.height) if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x -= 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x += 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = false collectionView.allowsSelection = false default: () } } animator.startAnimation() }
Combien de code. Permettez-moi d'expliquer ce qui se passe étape par étape:
- Tout d'abord, vérifiez si collectionView et index ne sont pas égaux à zéro. Sinon, nous ne pourrons pas démarrer l'animation.
- Ensuite, commencez à créer l'animation en appelant animator.addAnimations .
- Ensuite, enregistrez l'image actuelle, qui est utilisée pour la restaurer dans l'animation de convolution.
- Ensuite, nous définissons la valeur alpha pour descriptionLabel et closeButton pour les rendre visibles.
- Ensuite, supprimez le coin arrondi et définissez un nouveau cadre pour la cellule. La cellule sera affichée en plein écran.
- Ensuite, nous déplaçons les cellules voisines.
- Appelez maintenant la méthode animator.addComplete () pour désactiver l'interaction de l'image de la collection. Cela empêche les utilisateurs de le faire défiler pendant l'expansion des cellules. Modifiez également l'état actuel de la cellule. Il est important de changer l'état de la cellule, et ce n'est qu'après que l'animation se termine.
Ajoutez maintenant une animation de convolution. En bref, c'est juste que nous restaurons la cellule à son état précédent:
private func collapse() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.descriptionLabel.alpha = 0 self.closeButton.alpha = 0 self.layer.cornerRadius = self.cornerRadius self.frame = self.initialFrame! if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x += 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x -= 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = true collectionView.allowsSelection = true default: () } } animator.startAnimation() }
Il est maintenant temps de compiler et d'exécuter l'application. Essayez de cliquer sur la cellule et vous verrez l'animation. Pour fermer l'image, cliquez sur l'icône en forme de croix dans le coin supérieur droit.
Ajout du traitement gestuel
Vous pouvez prétendre obtenir le même résultat en utilisant
UIView.animate . Quel est l'intérêt d'utiliser un
UIViewPropertyAnimator ?
Eh bien, il est temps de rendre l'animation interactive. Ajoutez un
UIPanGestureRecognizer et une nouvelle propriété appelée
popupOffset pour suivre combien la cellule peut être déplacée. Déclarons ces variables dans la classe
CityCollectionViewCell :
private let popupOffset: CGFloat = (UIScreen.main.bounds.height - cellSize.height)/2.0 private lazy var panRecognizer: UIPanGestureRecognizer = { let recognizer = UIPanGestureRecognizer() recognizer.addTarget(self, action: #selector(popupViewPanned(recognizer:))) return recognizer }()
Ajoutez ensuite la méthode suivante pour enregistrer la définition de balayage:
override func awakeFromNib() { self.addGestureRecognizer(panRecognizer) }
Vous devez maintenant ajouter la méthode
popupViewPanned pour suivre le mouvement de balayage. Collez le code suivant dans
CityCollectionViewCell :
@objc func popupViewPanned(recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .began: toggle() animator.pauseAnimation() case .changed: let translation = recognizer.translation(in: collectionView) var fraction = -translation.y / popupOffset if state == .expanded { fraction *= -1 } animator.fractionComplete = fraction case .ended: animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) default: () } }
Il y a trois états. Au début du geste, nous initialisons l'animateur à l'aide de la méthode
bascule et le mettons immédiatement en pause. Pendant que l'utilisateur fait glisser la cellule, nous mettons à jour l'animation en définissant les propriétés du multiplicateur
fractionComplete . C'est la principale magie de l'animateur, qui leur permet de contrôler. Enfin, lorsque l'utilisateur relâche son doigt, la méthode d'animation
continueAnimation est appelée pour continuer l'animation. Ensuite, la cellule se déplacera vers la position cible.
Après avoir démarré l'application, vous pouvez faire glisser la cellule vers le haut pour la développer. Faites ensuite glisser la cellule développée vers le bas pour la réduire.
L'animation semble maintenant assez bonne, mais l'interruption de l'animation au milieu n'est pas possible. Par conséquent, pour rendre l'animation entièrement interactive, vous devez ajouter une autre fonction - l'interruption. L'utilisateur peut démarrer l'animation de développement / réduction comme d'habitude, mais l'animation doit être interrompue immédiatement après que l'utilisateur a cliqué sur la cellule pendant le cycle d'animation.
Pour ce faire, enregistrez la progression de l'animation puis tenez compte de cette valeur afin de calculer le pourcentage d'achèvement de l'animation.
Tout d'abord, déclarez une nouvelle propriété dans
CityCollectionViewCell :
private var animationProgress: CGFloat = 0
Mettez ensuite Ă jour le bloc
.began de la méthode
popupViewPanned avec la ligne de code suivante pour vous souvenir de la progression:
animationProgress = animator.fractionComplete
Dans le bloc
.changed ,
vous devez mettre à jour la ligne de code suivante afin de calculer correctement le pourcentage d'achèvement:
animator.fractionComplete = fraction + animationProgress
Maintenant, l'application est prête pour les tests. Exécutez le projet et voyez ce qui se passe. Si toutes les actions sont effectuées correctement en suivant mes instructions, l'animation devrait ressembler à ceci:
Animation inverse
Vous pouvez trouver une faille pour l'implémentation actuelle. Si vous faites glisser un peu la cellule, puis la ramenez à sa position d'origine, la cellule continuera de s'agrandir lorsque vous relâcherez votre doigt. Corrigeons ce problème pour améliorer encore l'animation interactive.
Mettons Ă jour le bloc
.end de la méthode
popupViewPanned , comme décrit ci-dessous:
let velocity = recognizer.velocity(in: self) let shouldComplete = velocity.y > 0 if velocity.y == 0 { animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) break } switch state { case .expanded: if !shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } case .collapsed: if shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if !shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } } animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)
Maintenant, nous prenons en compte la vitesse du geste pour déterminer si l'animation doit être inversée.
Et enfin, insérez une autre ligne de code dans le bloc
.changed . Mettez ce code Ă droite du calcul
animator.fractionComplete .
if animator.isReversed { fraction *= -1 }
Exécutons à nouveau l'application. Maintenant, tout devrait fonctionner sans faute.
Correction du geste de panoramique
Nous avons donc terminé la mise en œuvre de l'animation à l'aide de
UIViewPropertyAnimator . Cependant, il y a une erreur désagréable. Vous l'avez peut-être rencontrée lors du test de l'application. Le problème est que le défilement horizontal de la cellule n'est pas possible. Essayons de glisser vers la gauche / la droite à travers les cellules, et nous sommes confrontés au problème.
La raison principale est liée à l'
UIPanGestureRecognizer que nous avons
créé . Il
détecte également un geste de balayage et entre en conflit avec le reconnaisseur de gestes
intégré UICollectionView .
Bien que l'utilisateur puisse toujours faire défiler le haut / bas des cellules ou l'espace entre les cellules pour faire défiler les villes, je n'aime toujours pas une interface utilisateur aussi mauvaise. Corrigeons-le.
Pour résoudre les conflits, nous devons implémenter une méthode déléguée appelée
gestRecognizerShouldBegin (_ :) . Cette méthode contrôle si le reconnaisseur de gestes doit continuer à interpréter les touches. Si vous retournez
false dans la méthode, la reconnaissance des gestes ignorera les touches. Nous allons donc donner à notre propre outil de reconnaissance de panorama la possibilité d'ignorer les mouvements horizontaux.
Pour ce faire, définissons le
délégué de notre reconnaissance de panoramique. Insérez la ligne de code suivante dans l'initialisation
panRecognizer (vous pouvez placer le code juste avant la
reconnaissance de retour :
recognizer.delegate = self
Ensuite, nous implémentons la
méthode gestRecognizerShouldBegin (_ :) comme suit:
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return abs((panRecognizer.velocity(in: panRecognizer.view)).y) > abs((panRecognizer.velocity(in: panRecognizer.view)).x) }
Nous allons ouvrir / fermer si sa vitesse verticale est supérieure à la vitesse horizontale.
Ouah! Testons à nouveau l'application. Vous pouvez maintenant vous déplacer dans la liste des villes en balayant vers la gauche / droite les cellules.
Bonus: fonctionnalités de synchronisation personnalisées
Avant de terminer ce didacticiel, parlons des fonctions de synchronisation. Vous souvenez-vous toujours du cas où le développeur vous a demandé d'implémenter une fonction de synchronisation personnalisée pour l'animation que vous créez?
Habituellement, vous devez remplacer
UIView.animation par
CABasicAnimation ou envelopper dans
CATransaction .
Avec UIViewPropertyAnimator, vous pouvez facilement implémenter des fonctions de synchronisation personnalisées.
Les
fonctions de synchronisation (ou fonctions d'accélération) sont comprises comme des fonctions de vitesse d'animation qui affectent le taux de changement de l'une ou l'autre propriété animée. Quatre types sont actuellement pris en charge: EASYINOUT, EASYIN, EASYOUT, linéaire
Remplacez l'initialisation de l'animateur par ces fonctions de synchronisation (essayez de dessiner votre propre courbe cubique de Bézier) comme suit:
private lazy var animator: UIViewPropertyAnimator = { let cubicTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.17, y: 0.67), controlPoint2: CGPoint(x: 0.76, y: 1.0)) return UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTiming) }()
Alternativement, au lieu d'utiliser des paramètres de synchronisation cubiques, vous pouvez également utiliser la synchronisation à ressort, par exemple:
let springTiming = UISpringTimingParameters(mass: 1.0, stiffness: 2.0, damping: 0.2, initialVelocity: .zero)
Essayez de redémarrer le projet et voyez ce qui se passe.
Conclusion
Grâce à UIViewPropertyAnimator, vous pouvez améliorer les écrans statiques et l'interaction de l'utilisateur grâce à des animations interactives.
Je sais que vous ne pouvez pas attendre pour réaliser ce que vous avez appris dans votre propre projet. Si vous appliquez cette approche dans votre projet, ce sera très cool, faites le moi savoir en laissant un commentaire ci-dessous.
À titre de référence, vous pouvez
télécharger ici
le projet final .
Liens supplémentaires
Animations professionnelles utilisant UIKit -
https://developer.apple.com/videos/play/wwdc2017/230/Documentation UIViewPropertyAnimator pour les développeurs Apple -
https://developer.apple.com/documentation/uikit/uiviewpropertyanimator