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