• 30 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 11/2/23

Animez l’interface

C'est le moment de la touche finale de notre application ! Le coup de pinceau génial ! La cerise sur le gâteau ! Le clou du spectacle !

Tu t'emballes...

Oui. Pardon. Mais c'est parce que dans ce chapitre, nous allons parler des animations ! Et je trouve ça personnellement très amusant. Nous allons apprendre à animer nos vues pour donner à notre application une finition vraiment professionnelle.

Vous êtes prêt ?

Les animations en iOS

Il existe de nombreux moyens de faire des animations en iOS. Voici la plupart d'entre eux :

  • UIView Animation pour animer les propriétés des vues

  • Core Animation pour animer des vues, mais avec bien plus de possibilités

  • SceneKit pour des animations en 3D

  • SpriteKit pour des jeux en 2D

  • Dynamic Animation pour des animations avec des règles physiques comme la gravité, les collisions, etc.

Nous n'aborderons pas toutes ces techniques ensemble car ce serait trop long. Et puis, si vous ne programmez pas des jeux, les deux premiers couvrent 99% de vos besoins. Et le premier en couvre à lui tout seul 80%.

Donc nous allons nous concentrer dans ce chapitre sur UIView AnimationUIView Animation comme son nom l'indique permet d'animer les propriétés des vues et en particulier :

  • frame : nous avons vu cette propriété ensemble, elle permet de placer les vues

  • transform : nous venons de la voir

  • alpha : cette propriété permet de modifier l'opacité de la vue. En l'animant, on peut faire apparaître ou disparaître doucement notre vue.

  • backgroundColor : on peut animer le changement de la couleur de fond

Dans ce chapitre, nous allons principalement animer la propriété transform. Mais vous pouvez vous amuser à animer les autres. Nous allons réaliser l'animation suivante :

Cette animation a lieu en deux temps :

  • Lorsqu'on lâche la vue, la vue glisse vers la droite où vers la gauche selon l'endroit où nous l'avons lâchée.

  • Ensuite, elle réapparaît au milieu avec une animation comme si elle arrivait par le fond avec un effet "boing".

Créer une animation

Pour créer une animation, on utilise la méthode de classe animate de UIView. Cette méthode a plusieurs variantes, mais celle qui nous intéresse pour l'instant est la suivante :

UIView.animate(withDuration: TimeInterval, animations: () -> Void, completion: (Bool) -> Void)

Cette méthode prend en paramètre une durée de type TimeInterval. Rien de bien sorcier ici, il suffit de lui passer un nombre décimal. Cela correspond à la durée en secondes de l'animation.

Puis elle prend deux autres paramètres qui sont des f...

Des float ?

Mais non ! Regardez leur type, ce sont des fer..

Des fermetures !!

Bravo ! Les animations sont un très bon moyen de pratiquer les fermetures. Il n’est pas bien fait ce cours... ? :D

Dans la première fermeture, nous allons modifier les propriétés que l'on souhaite animer. Dans l'autre, on va pouvoir effectuer une action quand l'animation est terminée.

Faire disparaître la vue

Pour faire disparaître la vue, on va la faire glisser vers la droite si la réponse est vraie et inversement si la réponse est fausse. Pour être certains qu'elle quitte l'écran, nous allons la faire glisser d'une distance égale à la largeur de l'écran. On commence donc par obtenir la largeur de l'écran :

let screenWidth = UIScreen.main.bounds.width

Puis on va créer une translation vers la droite ou vers la gauche en fonction de la réponse choisie :

var translationTransform: CGAffineTransform
if questionView.style == .correct {
    translationTransform = CGAffineTransform(translationX: screenWidth, y: 0)
} else {
    translationTransform = CGAffineTransform(translationX: -screenWidth, y: 0)
}

Maintenant, nous allons créer l'animation :

UIView.animate(withDuration: 0.3, animations: {
    self.questionView.transform = translationTransform
}, completion: nil)

Je précise une durée de 0,3 seconde. Puis dans la fermeture animations, je modifie la propriété transform. Et UIView va s'occuper tout seul d'animer le changement de cette propriété.

Pour l'instant, je n'ai pas rédigé de code dans la fermeture completion mais nous allons corriger ça tout de suite !

UIView.animate(withDuration: 0.3, animations: {
    self.questionView.transform = translationTransform
}, completion: { (success) in
    if success {
        self.showQuestionView()
    }
})

La fermeture question a un paramètre success de type Bool qui permet de vérifier que l'animation s'est bien déroulée. Si c'est le cas, j'appelle la fonction showQuestionView que je vais créer tout de suite et qui contient le code suivant :

private func showQuestionView() {
    questionView.transform = .identity
    questionView.style = .standard

    switch game.state {
    case .ongoing:
        questionView.title = game.currentQuestion.title
    case .over:
        questionView.title = "Game Over"
    }
}

Dans cette fonction, j'ai simplement copié le code que nous avons rédigé dans le chapitre précédent et dont le seul but est de repositionner et mettre à jour la question.

Si je lance maintenant le simulateur, je vois bien que la vue disparaît sur le côté et à la fin de l'animation, elle revient au centre avec une nouvelle question :

Faire apparaître la question

Nous allons maintenant animer le retour de la question. Nous allons procéder ainsi :

  • Nous allons placer la vue au centre de l'écran et réduire sa taille pour qu'on ne la voie plus.

  • Nous allons animer son retour à sa taille normale avec un petit effet "boing".

Nous allons faire tout cela juste après l'animation précédente donc dans la méthode showQuestionView. Nous ramenons déjà la vue au centre de l'écran avec la ligne :

questionView.transform = .identity

Nous devons maintenant réduire sa taille. Et pour cela, nous allons utiliser un troisième initialiseur de CGAffineTransform :

CGAffineTransform(scaleX: CGFloat, y: CGFloat)

Cette transformation prend en paramètre deux échelles d'agrandissement en largeur sur les x et en hauteur sur les y. Si on veut doubler la largeur et tripler la hauteur, on écrit 2 et 3. Ici, on veut réduire la taille donc on va écrire :

questionView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)

La vue est maintenant de retour au centre et toute petite, tellement qu'on ne la voit plus. Nous allons animer son retour à sa taille normale. Pour obtenir l'effet "boing" souhaité, nous allons utiliser une autre version de la méthode animate :

UIView.animate(withDuration: TimeInterval, delay: TimeInterval, usingSpringWithDamping: CGFloat, initialSpringVelocity: CGFloat, options: [UIViewAnimationOption], animations: () -> Void, completion: (Bool) -> Void)

Cette méthode permet d'animer les propriétés de la vue en les faisant osciller autour de la valeur d'arrivée. Laissez-moi vous expliquer cela avec ces deux animations :

Dans la première animation, la vue va du point de départ au point d'arrivée simplement. Dans la deuxième, elle va plus rapidement au point d'arrivée et ensuite elle oscille autour du point d'arrivée avant de trouver sa position finale. On appelle cela une animation spring.

Et c'est ce que nous allons faire ici. Parcourons un peu les paramètres de cette grosse méthode :

  • duration : La durée de l'animation comme tout à l'heure.

  • delay : Cela permet de décaler le démarrage de l'animation. Nous n'en avons pas besoin ici.

  • damping : Ce paramètre peut être choisi entre 0 et 1. Plus on est proche de 0, plus il y aura d'oscillations autour de la valeur d'arrivée.

  • initialVelocity : Cela permet de choisir la vitesse de départ de la vue lors de l'animation. Plus elle sera rapide, plus les oscillations seront grandes.

  • options : Ici, on peut préciser des options pour notre animation. Ici, nous n'allons pas en avoir besoin, mais vous pouvez allez regarder les options disponibles ici.

  • animations et completion : les mêmes fermetures que pour la méthode utilisée précédemment.

Avec toutes ces informations, nous allons pouvoir utiliser notre méthode :

UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: [], animations: {
    self.questionView.transform = .identity
}, completion:nil)

A l'intérieur du bloc animation, je ramène la vue à sa taille d'origine en lui appliquant la transformation identité. Notre animation est terminée et notre vue question réapparaît maintenant avec un joli petit effet !

Tadaaa !
Tadaaa !

Et voilà ! Notre application est complètement finalisée ! Et c’est du travail de pro !

ViewController.swift

Pour que vous vous y retrouviez, voici l'intégralité de notre contrôleur !

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var newGameButton: UIButton!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    @IBOutlet weak var scoreLabel: UILabel!
    @IBOutlet weak var questionView: QuestionView!

    var game = Game()

    override func viewDidLoad() {
        super.viewDidLoad()
        let name = Notification.Name(rawValue: "QuestionsLoaded")
        NotificationCenter.default.addObserver(self, selector: #selector(questionsLoaded), name: name, object: nil)

        startNewGame()

        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(dragQuestionView(_:)))
        questionView.addGestureRecognizer(panGestureRecognizer)
    }

        @IBAction func didTapNewGameButton() {
            startNewGame()
        }

    private func startNewGame() {
        activityIndicator.isHidden = false
        newGameButton.isHidden = true

        questionView.title = "Loading..."
        questionView.style = .standard

        scoreLabel.text = "0 / 10"

        game.refresh()
    }

    func questionsLoaded() {
        activityIndicator.isHidden = true
        newGameButton.isHidden = false
        questionView.title = game.currentQuestion.title
    }

    func dragQuestionView(_ sender: UIPanGestureRecognizer) {
        if game.state == .ongoing {
            switch sender.state {
            case .began, .changed:
                transformQuestionViewWith(gesture: sender)
            case .ended, .cancelled:
                answerQuestion()
            default:
                break
            }
        }
    }

    private func transformQuestionViewWith(gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: questionView)

        let translationTransform = CGAffineTransform(translationX: translation.x, y: translation.y)

        let translationPercent = translation.x/(UIScreen.main.bounds.width / 2)
        let rotationAngle = (CGFloat.pi / 3) * translationPercent
        let rotationTransform = CGAffineTransform(rotationAngle: rotationAngle)

        let transform = translationTransform.concatenating(rotationTransform)
        questionView.transform = transform

        if translation.x > 0 {
            questionView.style = .correct
        } else {
            questionView.style = .incorrect
        }
    }

    private func answerQuestion() {
        switch questionView.style {
        case .correct:
            game.answerCurrentQuestion(with: true)
        case .incorrect:
            game.answerCurrentQuestion(with: false)
        case .standard:
            break
        }

        scoreLabel.text = "\(game.score) / 10"

        let screenWidth = UIScreen.main.bounds.width
        var translationTransform: CGAffineTransform
        if questionView.style == .correct {
            translationTransform = CGAffineTransform(translationX: screenWidth, y: 0)
        } else {
            translationTransform = CGAffineTransform(translationX: -screenWidth, y: 0)
        }

        UIView.animate(withDuration: 0.3, animations: {
            self.questionView.transform = translationTransform
        }, completion: { (success) in
            if success {
                self.showQuestionView()
            }
        })
    }

    private func showQuestionView() {
        questionView.transform = .identity
        questionView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)

        questionView.style = .standard

        switch game.state {
        case .ongoing:
            questionView.title = game.currentQuestion.title
        case .over:
            questionView.title = "Game Over"
        }

        UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: [], animations: {
            self.questionView.transform = .identity
        }, completion:nil)
    }
}

En résumé

  • Il existe de nombreux moyens de faire des animations en iOS. Mais ce que vous utiliserez dans la plupart des cas, ce sont les UIView Animation.

  • Les UIView Animation permettent d'animer facilement certaines propriétés de UIView comme framealphatransform et backgroundColor.

  • Pour créer une UIView Animation, on utilise l'une des variantes de la méthode de classe animate de UIView.

  • Les animations spring permettent de créer un effet d'oscillation autour de la valeur d'arrivée de l'animation.

Example of certificate of achievement
Example of certificate of achievement