TIM-Magazine.swift3

Note: Sous Xcode 9, il faut choisir un ‘deployment target’ inférieure à iOS 11.0.  Au besoin, installer un simulateur roulant la version 10.3 de iOS. 

À partir de iOS 11, les réseaux sociaux ne sont plus intégrés à la plate-forme.  Il faudra à l’avenir utiliser les frameworks natifs des réseaux sociaux.
Par exemple, pour twitter les instructions sont ici.

Contenu

TIM.Magazine.final1


Description

Avec ce tutoriel nous apprendrons à programmer une application iOS qui offre à l’utilisateur la possibilité d’habiller sa photo à partir de maquettes de magazines et de pouvoir publier le résultat sur les réseaux sociaux ‘facebook’ et ‘twitter’.
Vidéo du résultat final:
[embedyt]http://www.youtube.com/watch?v=CucHHI7697g[/embedyt]
Note: Le texte en rouge indique une tâche à réaliser en laboratoire.


Étape 1 – Réalisation de la scène principale

Action 1 – Créons un nouveau projet de type “Single View” pour le ‘iPad’.
TIM_Magazine_new_Xcode_project
Action 2 – Renseignons les information de création du projet
TIM_Magazine_new_TIM-Magazine
Action 3 – Ajoutons au projet un ‘groupe’ nommé ‘Mes ressources’.
TIM_Magazine_new_group TIM_Magazine_name_new_group


Voici les ressources du projet

TIM.Magazine.image002 TIM.Magazine.image003 TIM.Magazine.image001

Ajouter les images au projet

Action 4 – Ajoutons les trois images précédentes au projet.
Astuce:  Il faut enregistrer dans ‘download’ puis, glisser vers le projet.
Screenshot_2014-07-07_21_22_04 Screenshot_2014-07-07_21_24_54 Screenshot_2014-07-07_21_26_39 TIM_Magazine_cocher_-_copier_au_besoin TIM.Magazine.Ajouter groupe couvertures

Élaboration de la maquette de départ

Action 5 – Plaçons deux contrôles de type ‘UIButton‘ au bas de l’écran et renseignons les propriétés ‘Type‘ et ‘Image‘ pour changer leur apparence.
Elaborer la scene de depart

Définition des liens ‘MVC’ entre les boutons et le contrôleur de la scène

Action 6 – Glissons ‘ctrl+clic’ le bouton ‘facebook’ vers le le contrôleur de la scène principale.
TIM_Magazine_insert_IBAction
Action 7 – Renseignons les propriétés du lien  du bouton ‘facebook’
renseigner le IBAction TIM_Magazine_IBAction_code
Action 8 – Ajoutons le code suivant à la fonction ‘actionFacebook:

@IBAction func actionFacebook(sender: AnyObject) {
  println("actionFacebook")
}

Action 9 – Testons l’app dans le simulateur: emulateur01 Action 10 – Ajoutons une connexion sur le bouton ‘twitter’

//  ViewController.swift
//  TIM-Magazine.swift
//  2016.08 - Révision swift 3
import UIKit
class ViewController: UIViewController {
    /// Fonction exécutée suite à un clic du bouton 'facebook'
    @IBAction func actionFacebook(sender: AnyObject) {
        print("#actionFacebook\n")
    } // actionFacebook
    /// Fonction exécutée suite à un clic du bouton 'twitter'
    @IBAction func actionTwitter(sender: AnyObject) {
        print("#actionFacebook\n")
    } // actionTwitter
    /// Fonction exécutée suite au chargement de la scène en mémoire
    override func viewDidLoad() {
        super.viewDidLoad()
    } // viewDidLoad
} // ViewController


Étape 2 – Élaboration de base du premier magazine

Nous allons maintenant ajouter une scène supplémentaire à notre projet.
Action 11 – Ajoutons un nouveau groupe nommé ‘Les couvertures’ au projet.
groupe - couvertures
Action 12 – Dans le groupe ‘Les couvertures’, ajoutons un nouveau fichier.
Explication en classe de la notion de ‘XIB’ vs ‘StoryBoard’
nouvelle View
Action 13 – Sélectionnons un fichier de type ‘iOS – User Interface – View’
renseigner le fichier View
Action 14 – Nommons la nouvelle scène
nommer et enregistrer fichier View
Action 15 – Modifions le fond de la scène et ajoutons un titre.
elaboration de magazine01
Note: L’ajustement de l’alpha du fond de la scène nous permettra de voir les éléments sous la scène lorsqu’elle sera ajoutée à la scène principale.


Chargement d’une scène par programmation

Il nous reste maintenant à charger la scène ‘magazine01’ par programmation.
Idéalement, cette scène devrait être affichée au démarrage de l’application.
Rappel:  La scène est une instance de la classe UIViewController.
La classe ‘UIViewController’ contient un membre de type ‘UIView’ qui permet d’afficher des éléments à l’écran.
La classe ‘UIView’ propose la méthode ‘addSubview’ pour ajouter des élements visuels à son canvas d’affichage.
Donc, pour ajouter un élément à la scène principale, il suffit d’appeler la méthode ‘addSubview(un objet visuel)’ du membre ‘view’ de l’instance de la classe de la scène.
Par exemple, pour ajouter un ‘UILabel’ à l’écran  il suffit de programmer: view.addSubView(UILabel()).
Note: « self.view » est valide aussi.
Action 16 – Ajoutons le code suivant à la méthode ‘viewDidLoad’ du contrôleur de la scène principale.

override func viewDidLoad() {
 super.viewDidLoad()
 // Début de notre code...
       // Charger le fichier xib
        let ecranMagazine = UINib(nibName: "Magazine01", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
        // Ajuster la taille de la vue à la taille de la scène
        ecranMagazine.frame = self.view.bounds;
        // Ajouter la nouvelle ‘View’ à la scène principale
        view.addSubview(ecranMagazine);
    }
} //viewDidLoad

Action 17 – Exécutons le projet dans le simulateur
simulateur02
Solution:  Insérer la maquette avant les boutons.
Action 18 – Observons la structure des éléments de la scène
inserer magazine avant les boutons
Action 19 – Modifions le code, pour ajouter le magazine avant les boutons ‘facebook’ et ‘twitter’

// Insérer la nouvelle ‘View’ avant les boutons fb et twitter
print("#view.subviews.count: \(view.subviews.count)\n")
view.insertSubview(ecranMagazine, at: (view.subviews.count - 2) - 1 )
// Ou bien ceci pour ajouter au haut de la liste
view.insertSubview(ecranMagazine, at: 0)

Note: S’il y a des guides de mise en page, il faut les ajouter au calcul.
guides_de_mise_en_page
Ce qui donne le résultat suivant:

view.insertSubview(ecranMagazine, at: (view.subviews.count - (2 + 2)) - 1 )

Action 20 – Exécutons le projet dans le simulateur
simulateur03

Regroupement des éléments d’interaction

L’approche précédente pose un problème, il faudra modifier l’indice d’insertion à chaque fois que nous ajouterons des éléments de contrôle à la scène principale.
Par exemple, un menu pour changer de magazine.
Il est possible de regrouper des objets visuels ensemble grâce à l’objet ‘UIView’.
Action 21 – Ajoutons un objet ‘UIView’ à la scène principale.
ajouter viewMenu
Action 22 – Remplaçons la valeur de  paramètre ‘atIndex’ pour ‘view.subviews.count – 1″

// Avec les guides de mise en page
view.insertSubview(ecranMagazine, at: view.subviews.count - 3 )
// Sans guides
view.insertSubview(ecranMagazine, at: view.subviews.count - 1 )
// Oui bien, insérer la vue au fond de la scène principale:
view.insertSubview(ecranMagazine, at: 0)

simulateur avec menu

Quelques petits ajustements

Action 23 – Renseignons le fond de la scène principale à « noir » et le fond de la maquette ‘Magazine01′ à « Clear color’ (transparent).
scene fond a noir fond magazine01
Action 24 – Testons avec le simulateur:
etape01
Réflexion:  Nous assumons que la ‘view’ du fichier XIB sera toujours à la position [0] du XIB.  Si Apple change la position de la ‘view’, notre programme ne fonctionnera plus correctemnt.
La méthode recommandée par Apple pour charger la ‘view’ du fichier XIB est la suivante:

//: NOTE, Ne pas utiliser ce code dans votre projet!
var ecranMagazine2 = UIView()
// Parcourir tous les éléments du fichier
for x in UINib(nibName: "magazine01", bundle: nil).instantiate(withOwner: nil, options: nil) {
  // si objet courant de type 'UIView' alors renseigner notre variable
   if x.isKind(of:UIView.self) {
      print("#Nous avons trouvé la View!\n")
      ecranMagazine2 = x as! UIView
    } // if
} // for

Étape 03 – Design des maquettes

Action 25 – Vous devez réaliser les maquettes suivantes:
Note:  Pendant l’étape d’élaboration des maquettes, il est recommandé de fixer la couleur de la scène à noir.  Cela va permettre de distinguer le texte blanc. Nous changerons le fond de ‘noir’ à ‘transparent : clearcolor’ à l’étape de chargement des interfaces.
Astuce: Pensez à copier/coller le fichier Magazine01.xib vers 02..04.xib.

Magazine 01

magazine01

Magazine 02

magazine02

Magazine 03

magazine03

Magazine 04 (votre design perso)

magazine04

Les ressources
TIM.Magazine.image012 TIM.Magazine.image011Rolling_Stone_logo


Étape 04 – Tourner les pages avec un menu

Action 26 – Ajoutons un ‘UISegmentedControl’ dans la section interactive.
TIM.Magazine.segmented.control
TIM.Magazine.titre.segments
Résultat final:
TIM.Magazine.segmented.control.final
Définissons un lien IBAction sur le  ‘UISegmentedControl’
TIM.Magazine.segmented.control.IBAction

Plaçons une trace dans la méthode ‘actionTournerPages’ pour confirmer qu’il y a effectivement appel suite à une sélection.
/// Fonction exécutée suite à une sélection dans le 'UISegmentedControl'.  Note: Doit-être liée au UISC.
@IBAction func actionTournerPages(sender: UISegmentedControl) {
  let selection = sender.selectedSegmentIndex
  print("#actionTournerPages: la sélection vaut: \(selection)\n")
}

TIM.Magazine.segmented.control.selection

De façon empirique, nous constatons que l’objet ‘UISegmentedControl’ retourne une valeur entre 0 et nbÉléments-1.
Il ne reste plus qu’à programmer une structure logique qui insérera la couverture magazine correspondant à la sélection de l’utilisateur.
Note :
Pour enlever un objet de la scène il faut utiliser la syntaxe suivante :
ecranMagazine.removeFromSuperview()
Note: Il est important de déclarer la variable ‘ecranMagazine‘ comme propriété de la classe de la scène.
class ViewController: UIViewController {
var ecranMagazine =  UIView()

func chargerMagazine(no:Int) {
var nomMagazine = …
  ecranMagazine = UINib(nibName: nomMagazine, bundle: nil).instantiate(withOwner: nil, options: nil)[0as  UIView
  } // chargerMagazine
} //  ViewController

À faire par l’étudiant en laboratoire 
Programmer le chargement de la maquette de magazine correspondant au choix de l’utilisateur. 
Il est important de normaliser le code. Pas de structures lourdes dans votre code. Ne pas utiliser l’instruction ‘if’ ou ‘switch’. 
Il est possible de réaliser la tâche avec un maximum de 4 lignes de code : 
@IBAction func actionTournerPages(sender: AnyObject) {
// 1 - Retirer le magazine courant de la scène
// 2 - Construire une chaine de caractères qui décrit le nom du fichier: 'Magazine0' + selectionCourante + 1
// 3 - Charger le magazine à partir d’un nom d’un fichier NIB
// 4 - Ajouter le magazine à la scène principale
}

Résultat:
TIM.Magazine.final1  TIM.Magazine.final2  TIM.Magazine.final3  TIM.Magazine.final4


Étape 5 – Intégration des réseaux sociaux

PRÉ-REQUIS – Configuration des comptes sociaux sur l’appareil iOS.
TIM_Magazine_08
TIM_Magazine_07
Capture d’une Vue vers un objet UIImage

    func sauvegarderEcran(){
        // Description : méthode servant à capturer l’écran et au besoin,
        // en faire une sauvegarde dans l’album photos.
        // Cacher le menu
        leMenu.isHidden = true
        // 1 - Préparer un contexte de dessin à partir de la taille de la scène
        UIGraphicsBeginImageContext(self.view.bounds.size);
        // 2 – Dessiner à partir du claque par défaut de la scène
        self.view.layer.render(in: UIGraphicsGetCurrentContext()!);
        // 3 – Stocker le résultat dans notre objet local
        imageFinale = UIGraphicsGetImageFromCurrentImageContext()!;
        // 5 – Fermer le contexte de dessin
        UIGraphicsEndImageContext();
        // 6 – Facultatif - Stocker une copie de la capture d’écran dans l’album photos
        // Note:  À partir de iOS 10: Info.plist doit contenir une clé 'NSPhotoLibraryUsageDescription' de type 'String' qui indique à l'utilisateur comment sera utilisé sa librairie de photos.
        UIImageWriteToSavedPhotosAlbum(imageFinale, nil, nil, nil );
        leMenu.isHidden = false
    } // sauvegarderEcran

Action – Ajouter le code suivant dans la méthode posterSurFacebook:

sauvegarderEcran()

Action – Tester l’application:
Note: Sous iOS 10, il devrait y avoir l’erreur suivante:

TIM.Magazine[16985:6613925] [access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app’s Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.

Action: Ajouter au fichier ‘Info.plist’ la clé ‘NSPhotoLibraryUsageDescription’:
privacy-photo01
privacy-photo02
privacy-photo03
Attention: À partir de Xcode 9 il faut plutôt ajouter la clé ‘NSPhotoLibraryAddUsageDescription’
Ou bien en éditant le fichier info.plist et en ajoutant le code suivant:

	<key>NSPhotoLibraryUsageDescription</key>
	<string>Puis-je utiliser votre album photo?</string>

Action – Tester l’application:
Note: Sous iOS 10, il ne devrait plus y avoir d’erreur:
privacy-photo04
Action: Vérifions la présence de la capture dans l’album de photos du simulateur. (Maj+CMD+h)
album-photos01
album-photos02


Envoyer un ‘post’ vers Facebook

import Social
   func posterSurFacebook() {
        // Les étapes pour utiliser ‘facebook’
        // 1 - Tester si le service et les informations de connexion sont dispo
        if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook)
        {
            // 2 - Créer un feuille pour le 'post'
            if let controleur = SLComposeViewController (forServiceType: SLServiceTypeFacebook) {
            // 3 - Composer le message
            controleur.setInitialText("Test avec Swift 3 et Xcode 8");
            self.present(controleur, animated: true, completion: nil);
            // 4 - Ajouter une image - facultatif
            controleur.add(imageFinale);
            // 5 - Ajouter un lien - facultatif
            controleur.add(NSURL(fileURLWithPath: "http://tim.cstj.qc.ca") as URL!);
            // 6 - Présenter la fenêtre de confirmation à l'utilisateur
            // self.present(controleur, animated: true, completion: nil);
            // Ne fonctionne plus pour Facebook sous Xcode 8B5.  Par contre, fonctionne avec twitter.
            // Temporairement remplacer par:
            self.navigationController?.pushViewController(controleur, animated: true)
            }
        } // if Facebook
    } // posterSurFacebook

Action – Modifier la méthode ‘actionFacebook’.

    @IBAction func actionFacebook(sender: AnyObject) {
        print("actionFacebook")
        sauvegarderEcran()
        posterSurFacebook()
    } // actionFacebook

Action – Tester l’application
Attention – Assurez-vous de tester avec un simulateur sous iOS 10.  Les choses se sont sérieusement compliquées sous iOS 11.
post-sur-fb
Action – Modifier le méthode actionTwitter
Il reste à cacher la zone menu avant de capturer l’écran.
Définissons un IBOutlet sur la View du menu.
TIM_Magazine_06
Ajoutons le code suivant à la méthode capturerEcran.

    func sauvegarderEcran(){
       // Cacher le menu
        leMenu.isHidden = true
        // 1  ...
        // 6
        leMenu.isHidden = false
     } // sauvegarderEcran

Étape 6 – Utilisation d’un UIScrollView

Note: Cette étape est facultative.  Il n’y aura pas d’explication en classe, Si vous la réalisez, la correction du projet se fera sur 12/10.  Si vous optez pour cette option, il faudra le préciser dans votre archive de remise.
Le contrôle ‘UIScrollView’ permet d’afficher un contenu plus grand que sa zone d’affichage.
Une gestuelle sur l’écran donne accès au contenu non visible.
Méthode d’utilisation du contrôle:
1 – Placer le ‘UIScrollView’ sur une scène,
2 – Par programmation, ajouter des objets dans le ‘UIScrollView’ en renseignant correctement les positions x/y,
3 – Renseigner  la propriété  ‘UIScrollView’.contentSize

    func chargerLesMagazines() {
        // Renseigner la largeur de la zone de contenu du scrollView en fn du nb de pages.
        let pagesScrollViewSize = zoneAffichageDesElements.frame.size
        zoneAffichageDesElements.contentSize = CGSizeMake(pagesScrollViewSize.width * Float(nbMagazines), pagesScrollViewSize.height)
        for page in 0..<nbMagazines {
            // Placer les Views un à la suite de l'autre sur le plan des 'x'
            var frame:CGRect = zoneAffichageDesElements.bounds
            frame.origin.x = frame.size.width * Float(page)  // la première page (0) sera à x = 0
            frame.origin.y = 0
            // Charger le 'xib' courant
            var nomNIB = "Magazine0\(page+1)"
            println(nomNIB)
            if var newPageView = UINib(nibName: nomNIB, bundle: nil).instantiateWithOwner(nil, options: nil)[0] as? UIView {
                newPageView.frame = frame;
                // Ajouter au scrollView
                zoneAffichageDesElements.addSubview(newPageView)
            }  // if newPageView
        } // for page
    } // chargerLesMagazines()

Testons
Activons le ‘paging enable’
Ajoutons un UIPageControl (indicateur de la page courante/nbPages)
Expliquer Délégation

   // Une des Méthodes de délégation de UIScrollView
    func scrollViewDidEndDecelerating(scrollView: UIScrollView){
        println("scrollViewDidEndDecelerating")
        let positionPage = Int(zoneAffichageDesElements.contentOffset.x / zoneAffichageDesElements.frame.size.width)
        indicateurDePages.currentPage = positionPage
    }  // scrollViewDidEndDecelerating

Remplaçons  le code de la méthode ‘viewDidLoad.

    override func viewDidLoad() {
        super.viewDidLoad()
        // Début de notre code...
        // L'ancien code de chargement du magazine01 doit-être supprimé.
        chargerLesMagazines()
    }  // viewDidLoad

Un exemple de scrollView
FIN  du laboratoire


Document rédigé par Alain Boudreault – Juillet 2014 – Révision:  septembre 2017