0

Structure d’une app iOS

Salut tout le monde ! Après deux bons mois d’absence active (vacances obligent), nous reprenons en grande force les tutoriels sur notre ami commun : le Swift ! Au programme aujourd’hui, nous allons décortiquer l’armature d’une app iOS. On ne va pas faire de code mais essayer de comprendre comment tout s’organise dans l’appareil pour l’exécution d’une application. Si vous ne l’avez pas fait, je vous recommande de bien lire les bases des projets Xcode avant de vous aventurer ici 🙂

Types de fichiers

Créons un nouveau projet dans Xcode pour une application iOS et choisissons le template (le modèle) “Single View App” qui est le modèle pour une application vide sans contenu pré-rempli. Une fois les paramètres de l’app choisis, on voit dans le navigateur de fichier que des fichiers sont déjà présents.


La première ligne (l’icône de projet bleu) est le conteneur principal des fichiers de l’application. C’est également en cliquant là dessus que l’on va pouvoir accéder aux paramètres de compilation de l’application, notamment la signature de code, les plateformes de distribution, les versions minimales des logiciels, et tout un tas d’options très très utiles, nous y reviendrons plus tard.

On voit ensuite deux dossiers contenus dans le projet, l’un portant le nom de l’application que l’on a choisi lors la création du projet et un dossier “Products”. Vous aurez remarqué que les deux dossiers n’ont pas exactement le même icône pour les représenter, le petit triangle foncé dans l’angle inférieur gauche de l’icône de “Products” indique que le dossier est généré automatiquement par Xcode, mais aussi que les fichiers qu’il contient ne sont pas exactement stockés dedans, par exemple le fichier “Swiftement.app” qui est l’exécutable de mon application ne se trouve pas vraiment dans le dossier “Products”. Voyez vous-même :

C’est donc un moyen pratique de voir ce qui est important dans ce type de dossier sans s’embêter à fouiller dans les méandres de la logique d’Xcode.

On trouve dans le dossier contenant le code des fichiers .swift qui, sans surprise, contiennent du code en Swift, de fichiers .storyboard qui sont des fichiers d’interface, un fichier info.plist contenant un grand nombre d’informations sur l’app et un fichier Assets.xcassets avec un icône de dossier qui est un conteneur pour les images utilisées dans l’app. Voyons en détail ces fichiers.

AppDelegate.swift

Littéralement “représentant de l’application”, ce fichier contient le code permettant le relais entre le système d’exploitation (iOS) et notre app. Examinons rapidement son contenu :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }
}

N.B : j’utilise pour ce tutoriel Xcode 10 bêta 2 et Swift 4.2, il est possible que les commentaires ou certaines déclarations chez vous soient légèrement différentes, ce n’est pas important ici.

Ici nous pouvons voir la déclaration de la classe “AppDelegate” qui hérite des classes UIResponder et UIApplicationDelegate. On ne va pas parler du fonctionnement des classes et de la programmation orientée objet (Wikipedia), sachez seulement qu’il s’agit d’un groupement de fonctions conforme à un certain modèle (celui qui nous est donné par défaut par Xcode, entre autres) permettant l’interaction entre le système et l’app. Par exemple, lorsque l’on va fermer l’application de notre appareil, le système va le notifier à cette classe en appelant la fonction applicationDidEnterBackground(). On indique au système qu’il va se servir des fonctions présentes dans cette classe grâce à la ligne qui précède la déclaration de classe : @UIApplicationMain.

Main.storyboard et LaunchScreen.storyboard

Les fichiers .storyboard sont des fichiers permettant l’édition visuelle de l’interface de notre application. Par défaut, Xcode nous en fournit deux : un pour avoir un écran de lancement de l’application (LaunchScreen.storyboard) et un pour l’interface principal de notre app (Main.storyboard). Lorsque l’on édite l’un de ces fichiers, Xcode nous l’ouvre par défaut dans l’interface builder qui permet l’affichage et l’édition visuelle du contenu de ces fichiers. En réalité, il s’agit d’un code XML (Wikipedia) qui est seulement interprété par Xcode pour un affichage plus simple, voici ce que l’on y trouve, pour les plus curieux :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
        </scene>
    </scenes>
</document>

Je vous donne ce code mais nous n’aurons pratiquement jamais besoin de nous en servir ou de le modifier, c’est simplement pour vous donner un peu de contexte.

Revenons à nos moutons, les fichiers storyboard contiennent par défaut un contrôleur de vue (View Controller) représenté par un rectangle blanc dans l’éditeur généralement de la taille d’un écran d’iPhone.

Vue hiérarchique des éléments
Storyboard Entry Point
Vue
Modification de la taille de prévisualisation
Classe du contrôleur de vue

 

On remarque une petite flèche qui pointe vers ce cadre blanc qui symbolise le point d’entrée du Storyboard (Storyboard Entry Point), à savoir il désigne le contrôleur de vue qui sera appelé en premier lors du chargement de cet interface. Vu que pour l’instant nous n’en avons qu’un il pointe obligatoirement sur lui, mais plus tard nous aurons plusieurs contrôleurs différents par storyboard et il faudra indiquer à Xcode lequel charger en premier. Ce contrôleur de vue contient par défaut une vue (View) qui contiendra tout le contenu à afficher. On peut modifier la taille de prévisualisation de notre éditeur en bas pour visualiser l’affichage sur différentes tailles d’écran.

Si on sélectionne le contrôleur de vue (dans la vue hiérarchique, à gauche) pour le fichier Main.storyboard et que l’on affiche le sélecteur d’identité de l’objet on peut alors voir qu’il y a une classe qui lui est attribué, nommée par défaut ViewController (comme c’est original). Cela veut dire (en gros) que le code Swift lié à ce contrôleur sera contenu dans la classe ViewController, on pourra donc y noter toutes les fonctions relatives à ce que nous avons dans notre contrôleur, donc à l’affichage. A coté du nom de cette classe, nous avons d’ailleurs une petite flèche nous permettant d’afficher directement la classe concernée.

Au même endroit dans le fichier LaunchScreen.storyboard il n’y a pas de classe personnalisée d’entrée, ce qui veut dire qu’il n’y a pas de code personnalisé pour ce contrôleur.

ViewController.swift

Si on clique sur cette petite flèche, on ouvre tout de suite le fichier ViewController.swift qui contient la déclaration de la classe dont nous venons de parler. En voici son contenu :

1
2
3
4
5
6
7
8
9
10
11
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }


}

On déclare ici notre classe de type UIViewController qui contient par défaut une fonction viewDidLoad() qui sera appelée une fois le chargement du contrôleur effectué. C’est donc à cet endroit que nous pourrons écrire le code qui sera exécuté en premier dans notre application une fois l’affichage chargé.

Assets.xcassets

Le fichier assets est la librairie principale d’image de votre application. C’est dans ce “dossier” que seront rangées les images en incluant les diverses tailles et résolutions nécessaire pour les différents appareils. Et oui, chaque appareil va avoir sa résolution d’image native, ce qui est défini par la concentration de pixels sur l’écran. Par exemple, un iPad Air va avoir une résolution de 264 ppp (pixels par pouce, ppi en anglais pour pixels per inch) alors qu’un iPhone X aura une résolution de 458 ppp. Donc évidemment, pour qu’une image ait la même taille lorsqu’elle sera affichée sur un écran ou un autre, il faudra l’enregistrer dans des tailles différentes.


Par exemple, si on ajoute une image au catalogue (via un simple glisser déposé), Xcode va nous permettre de rajouter la même image avec d’autres résolutions, par défaut, il va nous demander une version deux et trois fois plus grande.

L’avantage, c’est que le système sélectionnera automatiquement la bonne version de l’image en fonction de l’appareil qui l’exécute sans que l’on ait à se soucier des tailles d’images et des résolutions d’écran dans le code.



Par défaut, il n’y a qu’un emplacement d’image pré-enregistré qui contiendra toutes les versions de l’icône de l’app. Comme vous pouvez le voir, il y en a beaucoup nécessaires.

Vous pouvez trouver la liste des tailles nécessaires sur le site Apple Developer. Fort heureusement, il y a des applications qui permettent de générer automatiquement des sets d’icônes à partir d’une seule image comme Icon Set Creator dont j’ai déjà parlé.

Info.plist

Le fichier info.plist (pour property list) est un fichier XML contenant des informations relatives à la compilation et l’exécution de l’app. Voici un aperçu de son contenu :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
</dict>
</plist>

Encore une fois, ce ne sera pas nécessaire d’éditer ou d’accéder très souvent à ce code. C’est la version code des informations que l’on entre dans les paramètres du projet (l’icône bleu, le premier que nous avons vu), par exemple les informations générales de l’app y sont comprises :

Mais il y a d’autres informations concernant les capacités de l’app, le sandboxing, etc… On remarque notamment la motion “Main storyboard file base name” qui contient la valeur “Main” qui indique donc que le fichier Main.storyboard est le fichier d’interface de base de l’app.

Résumons

En bref, lorsque l’on clique sur l’icône de notre app pour la lancer sur un iPhone, l’appareil va consulter dans info.plist (ce n’est pas vraiment le cas, en fait tout ça est compilé, le fichier info donne juste le chemin pour la compilation mais c’est plus simple expliqué comme ça) quel fichier d’interface il doit lancer. Dans le storyboard indiqué, l’app va rechercher le point d’entrée puis charger le contrôleur de vue associé dans le code. Une fois que tout ceci est en place, l’appareil interagit avec le code en appelant des fonction directement dans l’AppDelegate. Les interactions utilisateurs sont directement récupérables dans les contrôleurs de vue car le code à exécuter lorsque l’on touche l’écran ne sera pas le même selon le contrôleur affiché, mais ça on le verra la prochaine fois, je vous le donne quand même en exemple pour que vous ayez une vue complète du fonctionnement d’une app de base. Tenez, j’en ai même fait un schéma :

A bien noter que techniquement AppDelegate fait partie du code de l’application, mais il est réservé à l’interface avec l’appareil et la gestion globale du fonctionnement de l’app, le contenu n’y figure donc pas.

Je rappelle que cet exemple est donné pour une application extrêmement simple, et même carrément vide, il y a une multitude d’autres choses qui devraient figurer sur ce schéma, mais chaque chose en son temps, il est nécessaire de déjà bien comprendre le fonctionnement de base pour bien organiser son code !

J’espère que vous avez tout compris, et si ce n’est pas le cas n’hésitez pas à me le dire, je modifierai en conséquence l’article car ce sujet est assez complexe. Merci à tous pour votre attention et votre fidélité, on se retrouve très bientôt pour de nouvelles aventures !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *