
Remplacer
par
Introduire aux concepts de la programmation orientée objet (POO) sous le langage Swift ainsi qu’à la notion de programmation de protocoles.
Grace à Xcode, au langage Swift, à la POO sous Swift, à une suite d’images d’animation, à un protocole personnalisé, au concept de délégué, à un temporisateur, à la gestuelle d’écran, aux propriétés du cadre d’une vue, à la hiérarchie ‘vue/sous-vues’, nous bâtirons une application qui propose un micro système de type ‘aquarium/poissons à nourrir/plantes produisant de l’oxygène’.
[embedyt]http://www.youtube.com/watch?v=JhlEASo1AqM[/embedyt]
Action 1.1 – Ouvrons et exécutons le projet de départ.

Action 1.2 – Examinons la console de Xcode

Action 1.3 – Analysons le code de la classe de la scène principale.
//
// ViewController.swift
// Classe - Aquarium
//
// Created by Alain on 2014-10-11.
// Copyright (c) 2014 Production sur support. All rights reserved.
//
// ============================================================================================
// À l'usage exclusif des étudiants et étudiantes de
// Techniques d'Intégration Multimédia
// du cégep de Saint-Jérôme.
// --------------------------------------------------------------------------------------------
// Il est interdit de reproduire, en tout ou en partie, à des fins commerciales,
// le code source, les scènes, les éléments graphiques, les classes et
// tout autre contenu du présent projet sans l’autorisation écrite de l'auteur.
//
// Pour obtenir l’autorisation de reproduire ou d’utiliser, en tout ou en partie,
// le présent projet, veuillez communiquer avec:
//
// Alain Boudreault, aboudrea@cstj.qc.ca
//
// ============================================================================================
import UIKit
class ViewController: UIViewController{
// ***********************************************************************
// Définition des IBOutlet
// ***********************************************************************
@IBOutlet weak var labelNBPoissons: UILabel!
@IBOutlet weak var niveauNourriture: UIProgressView!
@IBOutlet weak var finPartie: UIView!
// ***********************************************************************
// Définition des propriétés de la classe ViewController
// ***********************************************************************
let maximumNourritureDisponible = 100
let unePortionDeNourriture = 20
var nourritureDisponible = 0
let nbPoissonsDepart = 5
var nbPoissons = 0
var modelesPoisson = [["nom":"p11", "nbImages":"8"],["nom":"p12", "nbImages":"5"], ["nom":"p13", "nbImages":"8"]]
// ***********************************************************************
// Définition des IBAction
// ***********************************************************************
@IBAction func recommencer(sender: AnyObject) {
println("recommencer")
commencerLaPartie()
} // recommencer
@IBAction func ajouterNourriture(sender: AnyObject) {
println("ajouterNourriture")
// Ajouter à la nourriture disponible un poucentage de la quant actuelle.
nourritureDisponible += Int(arc4random_uniform(UInt32(nourritureDisponible)))
// S'assurer que la quant de nourriture ne dépasse pas le maximum
nourritureDisponible = nourritureDisponible > maximumNourritureDisponible ? maximumNourritureDisponible : nourritureDisponible
actualiserProgressViewQuantNourriture()
} // ajouterNourriture
// ***********************************************************************
// Méthodes de la classe ViewController
// ***********************************************************************
override func viewDidLoad() {
println("viewDidLoad")
super.viewDidLoad()
// Ajouter des bulles
// TODO
// Préparer l'aquarium
commencerLaPartie()
} // viewDidLoad
/**
Préparer la scène et placer les poissons.
*/
func commencerLaPartie(){
println("commencerLaPartie")
// Masquer la View de fin de la partie
finPartie.hidden = true
nourritureDisponible = maximumNourritureDisponible
actualiserProgressViewQuantNourriture()
nbPoissons = nbPoissonsDepart
actualiserLabelNBPoissons()
// Ajouter des poissons
ajouterLesPoissons(nbPoissons)
} // commencerLaPartie
/**
Réactualiser l'indicateur du nombres de poissons.
*/
func actualiserLabelNBPoissons(){
println("actualiserLabelNBPoissons")
labelNBPoissons.text = "\(nbPoissons)"
} // actualiserLabelNBPoissons
/**
Réactualiser l'indicateur quantité de nourriture.
*/
func actualiserProgressViewQuantNourriture(){
println("actualiserProgressViewQuantNourriture")
niveauNourriture.progress = Float(nourritureDisponible) / 100.0
} // actualiserQuantNourriture
/**
Créer des poissons et les ajouter à la scène principale.
*/
func ajouterLesPoissons(quantite:Int){
println("ajouterLesPoissons")
// TODO
} // ajouterLesPoissons
override func didReceiveMemoryWarning() {
println("didReceiveMemoryWarning")
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
} // didReceiveMemoryWarning
} // Fin de la classe ViewController
Action 1.4 – Ajoutons un nouveau fichier Swift au projet.

Action 1.5 – Ajoutons le code suivant au fichier Poisson.Swift:
/**
Classe permettant de créer un gentil petit poisson rouge...
*/
class Poisson {
// Propriétés
// Constructeur: init()
// Destructeur: deinit
// Méthodes
} // class Poisson
Action 1.5 – Ajoutons le code suivant à la méthode ViewDidLoad:
Remarquez, notre commentaire apparaît dans le module de complétion du code:

let unPoisson = Poisson()
Voilà, nous venons de créer notre premier poisson! Par contre, c’est un poisson pas très utile car il n’a ni méthode ni propriété.
Action 1.6 – Ajoutons le code suivant à la classe Poisson
// Constructeur
init() {
println("Je suis le constructeur de Poisson.")
} // init
Action 1.7 – Exécutons l’application et analysons la console d’Xcode.

Action 1.8 – Ajoutons le code suivant à la classe Poisson
// Propriétés
/// Une chaine décrivant le poisson.
var nomPoisson:String
/// Le nom du fichier qui contient l'image.
var nomImage:String
var force:Int
let seuilDangerDeMort = 10
Action 1.9 – Observons l’erreur suivante:

Explication: En Swift, à moins d’être de type optionnel (uneVar?), les propriétés doivent-être initialisées avec du contenu, comme dans le cas de la propriété ‘seuilDangerDeMort’. Cette initialisation est faite dans le constructeur, règle générale, à partir des paramètres reçus.
Action 1.10 – Modifions la classe Poisson.
import Foundation
class Poisson {
// Propriétés
var nomPoisson:String
var nomImage:String
var force:Int
let seuilDangerDeMort = 10
// Constructeur
/**
Construire un poisson à partir d'un nom et d'une force
*/
init(nomPoisson:String, force:Int) {
println("Je suis le constructeur de Poisson.")
self.nomPoisson = nomPoisson
self.force = force
self.nomImage = "n/a"
} // init()
// Méthodes
/**
Je retourne une jolie description du poisson
*/
func quiSuisJe() ->String {
return "\n------------------\nJe suis le poisson \(nomPoisson) et j'ai une force de \(force)\n"
} // quiSuisJe()
} // class Poisson
Action 1.11 – Ajoutons le code suivante à la méthode ViewDidLoad:
let unPoisson = Poisson(nomPoisson: "Bob", force: 99)
println( unPoisson.quiSuisJe() )
Action 1.11b – Construisons une nouvelle classe à partir de la classe Poisson
class PoissonVolant: Poisson {
var hauteurDuVol:Int
init(nomPoisson: String, force: Int, hauteurDuVol:Int) {
// Note: Il faut initialiser les propriétés avant l'appel de la super classe.
self.hauteurDuVol = hauteurDuVol
super.init(nomPoisson: nomPoisson, force: force)
} // init
} // PoissonVolant
Action 1.11d – Testons avec le code suivant:
let unPoissonVolant = PoissonVolant(nomPoisson: "Exocoetidae", force: 99, hauteurDuVol: 66)
println(unPoissonVolant.quiSuisJe())
/// Résultat:
------------------
Je suis le poisson Exocoetidae et j'ai une force de 99
Action 1.11e – Surchargeons la méthode description()
/**
Je retourne une jolie description du poisson volant
*/
override func quiSuisJe() ->String {
return "\n------------------\nJe suis une poisson volant de type:\(nomPoisson) et je saute à \(hauteurDuVol) pied(s)\n"
} // quiSuisJe()
/// Résultat:
------------------
Je suis une poisson volant de type:Exocoetidae et je saute à 66 pied(s)
Le language Swift propose deux façons de regrouper des propriétés et des méthodes: soit par le mot clé ‘class’ ou le mot clé ‘struct’.
Par exemple, il est possible de créer un ensemble ‘propriétés/méthodes’ Poisson de la façon suivante:
/**
Structure Poisson. Les instances de ce type seront copiées lorsque passées en paramètre à une fonction ou une méthode.
*/
struct Poisson {
// Propriétés
// Constructeur: init()
// Destructeur: deinit
// Méthodes
} // struct Poisson
La différence étant que les instances de classe sont passées en référence – c-a-d, le paramètre reçu est l’instance originale de l’objet, alors que les structures sont passées par copie de l’objet.
Visible partout.
Visible dans tous les fichiers du projet (app) ou de la librairie.
Visible seulement dans le fichier courant.
Exemple,
class Poisson {
public var nomPoisson:String
var nomImage:String // internal par défaut
private var force:Int
private let seuilDangerDeMort = 10
// internal par défaut
init(nomPoisson:String, force:Int) {
println("Je suis le constructeur de Poisson.")
self.nomPoisson = nomPoisson
self.force = force
self.nomImage = "n/a"
} // init()
private func lireForce()-> Int{
return self.force
}
public func quiSuisJe() ->String {
return "Poisson de force: \(lireForce())"
} // quiSuisJe()
} // class Poisson
Attention: Ne pas modifier votre classe ‘poisson’
//
// Poisson.swift
// Projet - Aquarium
//
// Created by Alain on 15-10-16.
// Copyright (c) 2015 Production sur support. All rights reserved.
//
import Foundation
/**
Classe permettant de créer un gentil petit poisson rouge...
*/
class Poisson {
internal var _nomPoisson:String
var _nomImage:String // internal par défaut
private var _force:Int
private let _seuilDangerDeMort = 10
// computed properties
var force: Int {
get {
println("Dans le getter de 'force' du poisson \(_nomPoisson) avec une force de \(_force)")
if _force == 0 { return 3141592 }
return _force
}
set (valeur){
println("Dans le setter de 'force' du poisson \(_nomPoisson) avec une force de \(valeur)")
if valeur < 0 { _force = 0 }
}
}
// internal par défaut
init(nomPoisson:String, force:Int) {
println("Je suis le constructeur de Poisson.")
_nomPoisson = nomPoisson
_force = force
_nomImage = "n/a"
} // init()
private func lireForce()-> Int{
return _force
}
internal func quiSuisJe() ->String {
return "Poisson de force: \(lireForce())"
} // quiSuisJe()
} // class Poisson
Note: Une propriété de type ‘computed’ sans ‘setter’ sera alors de type ‘lecture seulement’.
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
println("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
println("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
// Note: Référence: https://developer.apple.com/library/ios/documentation/swift/conceptual/swift_programming_language/Properties.html
// Étant donné:
// self.nom = "Lafrance"
// self.titre = "Monsieur"
var properNom: String {
return self.titre + " " + self.nom
}
println("Je suis \(properNom).)
// Donne:
Je suis Monsieur Lafrance.
Note: ‘properNom’ n’est pas considérée comme étant une fonction mais une propriété calculée (computed propertie).
Les extensions de classe permettent d’ajouter à une classe existante:
Syntaxe:
extension UneClasse: UnProtocole, UnAutreProtocole {
// Programmation des protocoles
// Implémentation des nouvelles fonctionnalités
}
Note: Il est possible d’ajouter de nouvelles fonctionnalités à une classe mais pas de surcharger les existantes.
Par exemple,
extension String {
var majuscule: String { return self.uppercaseString }
}
println("bonjour à tous!".majuscule) // res.: BONJOUR À TOUS!
Action à réaliser en laboratoire par l’étudiant:
// Exemple d'utilisation de la classe Aquarium
let aquarium = Aquarium(individus: [
PoissonVolant(nomPoisson: "Exocoetidae", force: 99, hauteurDuVol: 66),
Poisson(nomPoisson: "Bob", force: 99)
],
dimension:100)
println(aquarium.quiSuisJe())
// Résultat:
Je suis un Aquarium de 100 gallon(s) avec les poissons suivants:
------------------
Je suis une poisson volant de type:Exocoetidae et je saute à 66 pied(s)
------------------
Je suis le poisson Bob et j'ai une force de 99
Indice: Le type des éléments du tableau reçu par le constructeur est Array<Poisson>. Ce tableau pourra stocker toutes instances dont l’ancêtre est de type Poisson (incluant le PoissonVolant). Cela s’explique par le concept de polymorphisme.
Action 1.12 –
// Constructeur de convenance
/**
Construire un poisson pas de nom et de force entre deux nombres.
*/
convenience init(deForceEntre:Int, etMax:Int){
self.init(nomPoisson:"Pas de nom", force: Int(arc4random_uniform(UInt32((etMax - deForceEntre) + 1))) + deForceEntre)
} // convenience init()
Action 1.13 – Ajoutons le code suivante à la méthode ViewDidLoad:
let junior = Poisson(deForceEntre: 33, etMax: 44)
println( junior.quiSuisJe() )
Ce qui va afficher dans la console:
viewDidLoad Je suis le constructeur de Poisson.
——————
Je suis le poisson Bob et j’ai une force de 99
Je suis le constructeur de Poisson.
——————
Je suis le poisson Pas de nom et j’ai une force de 3
Action 1.14 – Ajoutons les deux méthodes suivantes à la classe Poisson
/**
Méthode pour épuiser artificiellement le poisson
*/
func epuiserLePoisson(force:Int){
self.force -= force
if self.force < 10 {
println("Le poisson \(nomPoisson) est très très faible")
}
} // epuiserLePoisson
/**
Méthode pour nourrir le poisson
*/
func nourrirLePoisson(nourriture:Int){
force += 2 * nourriture
} // nourrirLePoisson
Action 1.15 – Ajoutons le code suivante à la méthode ViewDidLoad:
let unPoisson = Poisson(nomPoisson: "Bob", force: 99)
unPoisson.epuiserLePoisson(95)
unPoisson.nourrirLePoisson(40)
println(unPoisson.quiSuisJe())
Je suis le constructeur de Poisson.
Le poisson Bob est très très faible
——————
Je suis le poisson Bob et j’ai une force de 84
Référence Apple
Projet-Aquarium-etape1
À l’étape précédente, nous avons ajouté une méthode pour affaiblir le poisson.
Sous le seuil de force == 10, le poisson est placé en situation de danger de mort.
Le problème, c’est que notre application n’a aucune idée de cet état de fait.
Les protocoles, sous Swift, permettent d’indiquer à une classe abonnée qu’un intervention est requise par la classe offrant le protocole.
Par exemple, si la classe Poisson propose un protocole nommé « PoissonEtatDeSante » et qu’une autre classe du projet s’abonne à ce protocole alors cette dernière sera avisée des changements d’états du poisson.
Référence Apple
Action 2.1 – Étape 1 et 2 – Ajoutons le code suivant à la classe Poisson.
// Étape 1 - déclarer le protocole
protocol PoissonDelegate {
// étape 2 - déclarer les méthodes du protocole
func poissonAFaim(sender:Poisson)
}
// Note - Avant: class Poisson { ...
Note: Il est possible d’identifier des méthodes d’un protocole comme étant facultatives. Il faut alors déclarer le protocole de la façon suivante:
@objc protocol UnProtocole {
optional func uneMethodeFacultative(count: Int) -> Int
fun uneMethodeObligatoire(count: Int) -> Int
}
Action 2.2 – Étape 3 – Ajoutons le code suivant à la classe Poisson.
class Poisson {
// Propriétés
// Étape 3 - Définir une propriété - delegate - de type 'NomDuProtocole'
/// Propriété pour stocker le délégué éventuel de l'instance Poisson
var delegate:PoissonDelegate?
Action 2.3 – Étape 4 – Ajoutons le code suivant à la classe Poisson – seulement la ligne 6.
func epuiserLePoisson(force:Int){
self.force -= force
if self.force < 10 {
println("Le poisson \(nomPoisson) est très très faible - force < 10")
// Étape 4 - gérer le protocole
delegate?.poissonAFaim(self)
} // self.force < 10
} // epuiserLePoisson
Action 2.4 – Étape 5 – Ajoutons le code suivant à la classe abonnée
// Étape 5 - s'abonner au protocole
class ViewController: UIViewController, PoissonDelegate{
.
Action 2.5 – Étape 5 – Ajoutons le code suivant à la classe abonnée – seulement la ligne 5.
override func viewDidLoad() {
super.viewDidLoad()
unPoisson = Poisson(nomPoisson: "Bob", force: 99)
// Étape 6 - renseigner le delegate
unPoisson.delegate = self
Action 2.6 – Étape 5 – Ajoutons le code suivant à la classe abonnée
// Étape 7 - Programmer les méthodes du protocole
func poissonAFaim(sender: Poisson) {
println("Le poisson \(sender.nomPoisson) a faim, je vais donc le nourrir!")
sender.nourrirLePoisson(20)
} // poissonAFaim
Action 2.7 – Exécutons l’application

Action à réaliser en laboratoire par l »étudiant:
Dans la classe abonnée:
Action 3.1 – Dans la classe Poisson, ajoutons le code suivant
Note: À cette étape, l »image du poisson sera codée en dur.
// 1 - Importer le frameWork UIKit
import UIkit
// 2 - Classe Poisson étend UIImageView
class Poisson:UIImageView {
Note: Suite à l’ajout d’une classe à étendre dans la déclaration de la classe Poisson, nous obtenons l’erreur suivante:

Action 3.2 – Confirmons le Fix-it*, ce qui ajoutera le code suivant à la classe:
/**
Cette méthode est obligatoire lorsque la super classe est de type 'Fondation'
*/
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
*Il faudra aussi faire cette correction avec la classe PoissonVolant.
Note: Il y a encore une erreur à corriger – l’appel du constructeur de la super classe.

Action 3.3 – Modifions le constructeur de la classe Poisson pour y ajouter l »appel du constructeur de la super classe.
// 3 - Modification du constructeur de la classe Poisson
// Constructeur
/**
Construire un poisson à partir d'un nom et d'une force
*/
init(nomPoisson:String, force:Int) {
println("Je suis le constructeur de Poisson.")
self.nomPoisson = nomPoisson
self.force = force
self.nomImage = "n/a"
// Étape 3.3 - Appel de super.init()
// Note: Il faut renseigner les propriétés locales
// avant l'appel de super.init()
// Il faut utiliser le constructeur de la super classe
// Il n'est pas possible d'utiliser un init de convenance de la super classe (à valider sous 6.1).
// Le contructeur de UIImageView est: init(image: UIImage!)
// Par contre, il ne permet pas de préciser une taille pour l'image.
// Nous allons donc faire appel au contructeur de la super classe de UIImageView C-A-D, UIView(frame: CGRect)
//
// Créer une View à la position (20,150) de dimension (100, 50)
super.init(frame: CGRectMake(20, 150, 100, 50))
image = UIImage(named: "p13-frame-1.gif")
} // init()
Action 3.3b – Ajoutons le poisson à la vue de la scène.
view.addSubview(unPoisson)
Action 3.4 – Testons l’application.

Note: La classe poisson possède maintenant une interface visuelle de type « UIView » et peut être ajoutée à la vue d’une scène.
La classe ‘UIImage’ propose des méthodes et des propriétés pour animer une séquence d’images.
Action 3.5 – Ajoutons le code suivant à la fin du constructeur de la classe Poisson.
// Créer l'animation à partir d'une suite d'images
var images:[UIImage] = []
// Note: Il y a 8 images pour l'animation du poisson P13
for i in 1...8 {
// Note: Xcode 6.1 - UIImage(named:) retourne une optionnelle.
images.append(UIImage(named:"p13-frame-\(i).gif"))
}
animationImages = images // Un tableau d'images pour l'animation
animationDuration = 1.5 // La durée de l'animation
startAnimating() // Démarrer l'animation
// } // init()
Action 3.6 – Testons l’application
Note: Le poisson devrait maintenant être animé à l’écran.

Note: Si le nom du dossier contenant les images de l’animation se termine par ‘.atlas’ Xcode va alors créer automatiquement une ‘sprite sheet’.
Exemple d’une sprite sheet:
![]()
Action 3.7 – Ajoutons un nouveau constructeur, à notre classe Poisson, qui recevra le nom de l’image, la position, la dimension et le délégué du protocole PoissonDelegate.
// Constructeur
/**
Construire un poisson animé à x,y hauteur et largeur et délégué
*/
init(nomPoisson:String, nomImage:String, nbImages:Int, posX:CGFloat, posY:CGFloat, largeur:CGFloat, hauteur:CGFloat, leDelegate:PoissonDelegate) {
println("Je suis le constructeur de \(nomPoisson).")
self.nomPoisson = nomPoisson
self.force = 20
self.nomImage = nomImage
delegate = leDelegate
// Note: Il faut renseigner les propriétés locales
// avant l'appel super.init()
super.init(frame: CGRectMake(posX , posY, largeur, hauteur))
// Image sans anime - poisson mort
image = UIImage(named: "mort.png")
// Créer l'animation à partir d'une suite d'images
var images:[UIImage] = []
for i in 1...nbImages {
images.append(UIImage(named: "\(nomImage)-frame-\(i).gif")!)
} // for nbImages
animationImages = images
animationDuration = 1.5
startAnimating()
} // init()
Action 3.8 – Et remplaçons le code de viewDidLoad par:
override func viewDidLoad() {
println("viewDidLoad")
super.viewDidLoad()
// TODO: Ajouter des bulles
let x = Poisson(nomPoisson: "Poisson 1", nomImage: "p11", nbImages: 8, posX: 150, posY: 250, largeur: 100, hauteur: 50, leDelegate: self)
x.epuiserLePoisson(99)
view.addSubview(x)
// Préparer l'aquarium
commencerLaPartie()
} // viewDidLoad
Action 3.9 – Testons l’application.

Note: Remarquez que le poisson répond au protocole PoissonDelegate.

Action 3.10 – Créons plusieurs poissons, dans la méthode ‘ajouterPoissons’ à partir des enregistrements du tableau ‘modelesPoisson’.
Note: Il faut effacer le poisson que nous avons créé dans ViewDidLoad.
/**
Créer des poissons et les ajouter à la scène principale.
*/
func ajouterLesPoissons(quantite:Int){
println("ajouterLesPoissons")
// Poisson 1, pour tester le protocole
let x = Poisson(nomPoisson: "Poisson 1", nomImage: "p11", nbImages: 8, posX: 50, posY: 180, largeur: 100, hauteur: 50, leDelegate: self)
x.epuiserLePoisson(99)
view.addSubview(x)
// Ou bien directement, sans passer par une variable.
view.addSubview(Poisson(nomPoisson: "Poisson 2", nomImage: "p11", nbImages: 8, posX: 50, posY: 120, largeur: 75 , hauteur: 37, leDelegate: self))
view.addSubview(Poisson(nomPoisson: "Poisson 3", nomImage: "p11", nbImages: 8, posX: 130, posY: 120, largeur: 100 , hauteur: 50, leDelegate: self))
view.addSubview(Poisson(nomPoisson: "Poisson 4", nomImage: modelesPoisson[2]["nom"]!, nbImages: (modelesPoisson[2]["nbImages"]!).toInt()!, posX: 75, posY:400, largeur: 100 , hauteur: 50, leDelegate: self))
view.addSubview(Poisson(nomPoisson: "Poisson 5", nomImage: modelesPoisson[2]["nom"]!, nbImages: (modelesPoisson[2]["nbImages"]!).toInt()!, posX: 160, posY: 450, largeur: 100 , hauteur: 50, leDelegate: self))
view.addSubview(Poisson(nomPoisson: "Poisson 6", nomImage: modelesPoisson[1]["nom"]!, nbImages: (modelesPoisson[1]["nbImages"]!).toInt()!, posX: 220, posY: 290, largeur: 100 , hauteur: 60, leDelegate: self))
} // ajouterLesPoissons
Note: Il faut aussi ajuster le nombre de poissons de départ à 6:
let nbPoissonsDepart = 6
Action 3.11 – Testons l’application


Projet-Aquarium-etape3
À cette étape, nous donnerons vie à notre poisson grace à un temporisateur (NSTimer) de 2 secondes. À chaque intervalle, il sera possible d’exécuter du code dans le but de modifier certains paramètres de la classe. Par exemple, le niveau d’énergie du poisson, son état (vivant/mort), …
Action 4.1 – Ajoutons le code suivant à la classe Poisson.
// Propriétés de la classe Poisson
var unTimer = NSTimer()
let intervalleTemps:NSTimeInterval = 2
// Ajouter ces lignes à la fin du constructeur
unTimer = NSTimer.scheduledTimerWithTimeInterval(intervalleTemps, target: self, selector: Selector("gererLeProtocoleDelegate:"), userInfo: nil, repeats: true)
} // init
// Ajouter cette méthode à la classe Poisson
/**
Méthode de gestion PoissonDelegate
*/
func gererLeProtocoleDelegate(timer: NSTimer){
//println("testerDelegate de \(nom)")
let impact = Int(arc4random_uniform(5))
force-=impact
delegate?.poissonAFaim(self)
} // testerDelegate
Action 4.2 – Ajoutons la valeur de la force dans la trace de la méthode poissonAFaim.
println("Le poisson \(sender.nomPoisson), force de:\(sender.force) a faim, je vais donc le nourrir!")
Action 4.3 – Testons l »application.

Action 4.4 – Ajoutons, dans la classe Poisson, la déclaration de deux méthodes au protocole PoissonDelegate.
func poissonEnDangerDeMort(sender:Poisson) func poissonEstMort(sender:Poisson)
Action 4.5 – Modifions la méthode gererLeProtocoleDelegate.
func gererLeProtocoleDelegate(timer: NSTimer){
let impact = Int(arc4random_uniform(5))
force-=impact
delegate?.poissonAFaim(self)
if force < seuilDangerDeMort {
delegate?.poissonEnDangerDeMort(self)
stopAnimating()
} // force < 10
if force <= 0 {
unTimer.invalidate()
//timerDeplacement.invalidate()
delegate?.poissonEstMort(self)
} // force <= 0
} // testerDelegate
Note: La classe abonnée n’est plus conforme au protocole PoissonDelegate.
Action 4.6 – Ajoutons les deux méthodes suivantes à la classe ViewController.
func poissonEnDangerDeMort(sender: Poisson) {
println("\(sender.nomPoisson) a une force de \(sender.force) et il va mourrir de faim!")
}
func poissonEstMort(sender: Poisson) {
println("\(sender.nomPoisson) est mort")
} // poissonEstMort
Action 4.7 – Cessons de nourrir le poisson lorsqu’il a faim.
func poissonAFaim(sender: Poisson) {
println("Le poisson \(sender.nomPoisson), force de:\(sender.force) a faim, je vais donc le nourrir!")
// sender.nourrirLePoisson(20)
} // poissonAFaim
Action 4.8 – Testons l’application


Éventuellement, les poissons vont tous mourrir.
Il faudrait les enlever de la scène à leur décès et gérer l’indicateur de poissons restants.
Action 4.9 – Effaçons le poisson mort.
// ViewController.swift
func poissonEstMort(sender: Poisson) {
println("\(sender.nomPoisson) est mort")
//sender.unTimer.invalidate()
sender.removeFromSuperview()
nbPoissons--
actualiserLabelNBPoissons()
} // poissonEstMort
Note: L’instance de la classe poisson va persister même suite à un .removeFromSuperview car le NSTimer maintient une référence à la méthode gererLeProtocoleDelegate de la classe poisson. Le message ‘poissonEstMort’ va continuer à être envoyé à la classe abonnée aussi longtemps que le ‘Timer’ va être actif.
Pour désactiver le Timer il faut appeler la méthode ‘invalidate()’ de ce dernier.
Action 4.9b – Ajoutons la méthode suivante à la classe Poisson.
// Destructeur de la classe Poisson
// Faire le ménage ici. Par exemple, fermer des connexions, ...
// Attention: l'objet ne sera pas détruit s'il y a des Timers qui tournent.
deinit{
println("Je suis le fossoyeur de l'objet \(nomPoisson)")
} // deinit
Action 4.10 – Testons l’application
[table]
, 
[/table]
Action 4.11 – Affichons le panneau de fin de partie lorsque le nombre de poissons sera == 0.
func poissonEstMort(sender: Poisson) {
println("\(sender.nomPoisson) est mort")
sender.removeFromSuperview()
nbPoissons--
actualiserLabelNBPoissons()
if nbPoissons == 0 {
finPartie.hidden = false
} // if nbPoissons == 0
} // poissonEstMort
Action 4.12 – Testons l’application

Action 4.13 – Analysons la méthode ‘commencerLaPartie’
func commencerLaPartie(){
println("commencerLaPartie")
// Masquer la View de fin de la partie
finPartie.hidden = true
nourritureDisponible = maximumNourritureDisponible
actualiserProgressViewQuantNourriture()
nbPoissons = nbPoissonsDepart
actualiserLabelNBPoissons()
// Ajouter des poissons
ajouterLesPoissons(nbPoissons)
} // commencerLaPartie
Action 5.1 – Modifions la propriété ‘userInteractionEnabled’ de la classe poisson, pour permettre à UIImageView de recevoir les interactions de l’utilisateur.
// Constructeur de la classe Poisson
// init(nomPoisson:String, nomImage:String, posX:CGFloat, posY:CGFloat, taille:CGFloat, leDelegate:PoissonDelegate, nbImages:Int) {
userInteractionEnabled = true
// } // init
Action 5.2 – Nourrir le poisson suite à une gestuelle
// Étape 5 - Ajouter la méthode suivante:
// Méthode exécutée lorsque l'écran est touché.
// touch.view pointe sur la View qui a reçu l'événement
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// println(touches.anyObject())
super.touchesBegan(touches, withEvent: event)
let touch : UITouch = touches.first as! UITouch
/*
if touch.view.isKindOfClass(Poisson)
{
}
*/
if let poissonCourant = touch.view as? Poisson {
println("C'est un poisson!")
if nourritureDisponible >= unePortionDeNourriture {
poissonCourant.nourrirLePoisson(unePortionDeNourriture)
nourritureDisponible -= unePortionDeNourriture
actualiserProgressViewQuantNourriture()
}
} // if touch.view as? Poisson
} // touchesBegan
Action 5.3 – Modifions la classe Poisson.
/**
Méthode pour nourrir le poisson
*/
func nourrirLePoisson(nourriture:Int){
force = nourriture
if force > seuilDangerDeMort {
startAnimating()
}
} // nourrirLePoisson
Action 5.4 – Testons l’application (regardez l’indicateur de nourriture descendre et les poissons revenir à la vie)
Note: la méthode ajouterNourriture() est déjà fonctionnelle.

Projet-Aquarium-etape5
Note: Voici comment masquer la barre d’état du téléphone
override func prefersStatusBarHidden() -> Bool {
return true
}
Il faut créer une classe Plante qui:
Il faut:
// Effacer les plantes
for unObjetDeLascene in view.subviews {
if let plante = unObjetDeLascene as? Plante {
plante.unTimer.invalidate()
plante.removeFromSuperview()
}
} // for unObjetDeLascene
Action 7.1 – Ajoutons des propriétés à la classe poisson
// Étape 7
var timerDeplacement = NSTimer()
let intervalleTempsDeplacement:NSTimeInterval = 1/15
var _largeurEcran:CGFloat = 0
var _hauteurEcran:CGFloat = 0
Action 7.2 – Ajoutons une méthode pour obtenir la dimension de l’appareil
// Etape 7
func determinerDimensionsEcran(){
// Déterminer la résolution de l'appareil
let screenRect:CGRect = UIScreen.mainScreen().bounds
_largeurEcran = screenRect.width
_hauteurEcran = screenRect.height
println(screenRect)
} // determinerDimensionsEcran
Action 7.3 – Ajoutons le code suivant au constructeur de Poisson
// Étape 7 - Créer un NSTimer pour le déplacement du poisson
timerDeplacement = NSTimer.scheduledTimerWithTimeInterval(intervalleTempsDeplacement, target: self, selector: Selector("deplacerObjet:"), userInfo: nil, repeats: true)
determinerDimensionsEcran()
Action 7.4 – Calculons la nouvelle position du poisson
func deplacerObjet(timer: NSTimer){
let vitesse:CGFloat = 3
let largeurImage = self.frame.width / 2
// Note: Les poissons vont de droite à gauche, sauf pour p12
let _direction:CGFloat = nomImage == "p12" ? -1 : 1
var position = self.center
position.x = self.center.x - (vitesse * _direction)
// Ajuster la position si le poisson sort de l'écran
if (_direction == 1 && (position.x + largeurImage) < 0){
// À la fin, ajuster avec la largeur du view
position.x = _largeurEcran + largeurImage
}
if (_direction == -1 && (position.x - largeurImage) > _largeurEcran){
// Au debut, ajuster avec la largeur du view
position.x = -largeurImage
}
// Renseigner la nouvelle position du poisson
self.center = position;
} // deplacerObjet
Action 7.5 – Testons l’application
Note: Voici comment programmer un mouvement un peu plus naturel aux poissons:
position.y = sin(self.center.x / 100) * 30 + postionYDepart
Action 7.5 – Ajoutons les fichiers suivants au projet: aquarium.sons
Action 7.6 – Ajoutons le code suivant au projet.
// 1 - Importer le kit AudioVideo
import AVFoundation
// 2 - À ajouter dans la zone de définition des propriétés de la classe ViewController
var playerTrameSonoreAquarium = AVAudioPlayer()
// 3 - Méthode à ajouter dans la classe ViewController
func ChargerTramesSonores(){
// Permettre la lecture en arrière plan.
// AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
// AVAudioSession.sharedInstance().setActive(true, error: nil)
let URLTrameSonoreAquarium = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("eau", ofType: "mp3")!)
var error:NSError?
playerTrameSonoreAquarium = AVAudioPlayer(contentsOfURL: URLTrameSonoreAquarium, error: &error)
playerTrameSonoreAquarium.prepareToPlay() // Charger le fichier son en mémoire
playerTrameSonoreAquarium.numberOfLoops = -1 // Boucle à l'infini avec une valeur négative
playerTrameSonoreAquarium.volume = 0.2
} // ChargerTramesSonores
// 4 - À ajouter dans la méthode ViewDidLoad()
ChargerTramesSonores()
// 5 - À ajouter dans la méthode commercerLaPartie()
playerTrameSonoreAquarium.play()
Projet-Aquarium-fin-etape-7-sans-parties-labo

Note: Placer un poisson avant une plante: il est possible d’insérer une View avant une autre:
view.insertSubview(Poisson(
nomPoisson: « Poisson \(compteur++)« ,
nomImage: poissonCourant.nom,
nbImages: poissonCourant.nbImagesAnimation,
posX: _positionX * poissonCourant.directionDeplacement,
posY: _positionY,
largeur: _largeur,
hauteur: _hauteur,
vitesseDeplacement:_vitesse,
directionDeplacement:poissonCourant.directionDeplacement ,
leDelegate: self),
belowSubview: planteAuHasard)

FIN du document