Swift pour OS X

Aujourd'hui nous allons créer une minuscule application pour Mac OS X avec le nouveau langage de programmation d'Apple, Swift.

Ce que fait cette app est anecdotique : encode du texte vers de l'alphabet OTAN ou décode de l'alphabet OTAN vers du texte.

Mais c'est un bon support car le principe est facile à comprendre et la mise en oeuvre est également très simple, bien que la route à suivre soit longue et tortueuse.

Idéal donc pour un p'tit tuto et une introduction à cet univers…

Article mis-à-jour le 2015-06-11

Les exemples sont écrits pour Swift 1.2 mais des mises-à-jour pour Swift 2.0 sont présentes à la fin de certains chapitres du tutoriel.

Introduction

Langage interprété

Question programmation, sur ce blog, d'habitude, c'est plutôt Ruby, ou parfois Python.

Tous deux sont des langages interprétés : le code de haut niveau, Ruby par exemple, est lu, décodé puis exécuté séquentiellement, dans l'ordre du script.

Quand on lance une application Ruby, on lance en fait Ruby qui à son tour analyse puis exécute les fichiers contenant le code de l'application lancée, par un processus complexe de compilation du programme à la volée.

En effet votre système Mac OS X ne peut exécuter que du code binaire destiné à Mac OS X : quand on code en Ruby -le langage-, c'est ensuite Ruby -le moteur- qui transforme ce langage, par étapes, en code binaire destiné au système d'exploitation (qui à son tour va en contrôler l'exécution par les processeurs).

Langage compilé

Avec Swift c'est différent : c'est un langage compilé, tout comme son prédécesseur Objective-C.

Cela signifie qu'avant de pouvoir exécuter l'application codée en Swift, il faut d'abord la compiler, c'est-à-dire la transformer en code binaire compréhensible par OS X.

La transformation du code de haut niveau (proche de l'humain) en code de bas niveau (proche de la machine) se fait une fois pour toutes, et le résultat est un fichier (ou plutôt un bundle de fichiers pour OS X) qui est votre app, prête à être lancée.

Elle sera d'ailleurs exécutée directement par le système, c'est ce qu'on appelle une application native.

En Ruby, le code est compilé à la volée par Ruby au fur et à mesure qu'il est exécuté ; avec Swift, le code est compilé une seule fois, créant un fichier MonApplication.app prêt à fonctionner sans avoir besoin de le lancer à travers Swift.

Apple

Pour créer une application native Apple, il faut utiliser un langage qui sache manipuler les bibliothèques, frameworks et APIs fournis par Apple.

De très nombreux langages de programmation en sont capables, mais officiellement c'est Objective-C qui est le langage de choix.

Objective-C est un ajout, une surcouche à C, le transformant en langage orienté objet.

Au début de l'été 2014 Apple a dévoilé un nouveau langage de programmation destiné aux plateformes Apple (OS X, iOS), Swift.

C'est un langage “moderne” qui offre l'avantage d'être plus accessible, question syntaxe, que Objective-C qui est un peu rébarbatif.

Swift emprunte de nombreux paradigmes à Ruby, Python ou même Haskell.

C'est-à-dire que Swift essaie de prendre le meilleur de la programmation objet et le meilleur de la programmation fonctionnelle pour en faire quelque chose d'unique.

Swift est encore jeune et souffre de ce statut par le fait qu'il évolue très vite, rendant encore complexe la gestion de projets sur le long terme, et par le fait qu'il existe encore très peu de bibliothèques externes disponibles.

Cependant cette situation évolue elle-même rapidement et l'on voit apparaître de nombreux projets, et l'équipe derrière Swift est très compétente (des anciens Unix nerds et quelques jeunes génies) et réactive, ne manquant pas d'innovation pour combler les rares lacunes rapidement.

Quand Swift sera vraiment mature, étant donné ses qualités actuelles déjà remarquables, ce sera de fait le remplaçant du vaillant Objective-C. Pour le moment, les deux cohabitent plus ou moins joyeusement.

Outils

Pour coder en Ruby ou Python vous pouvez utiliser n'importe quel éditeur de texte, qu'il soit spécialisé pour le code ou pas.

Il existe aussi des IDE, des applications qui font éditeur de code mais aussi offrent une assistance permanente au développeur (complétion en temps réel, manipulation des fichiers, versioning, etc).

Bien qu'il existe de rares outsiders, pour développer sur plateforme Apple il faut utiliser leur outil dédié, Xcode.

C'est un gros IDE qui gère votre projet (l'application), de sa création initiale à la compilation finale.

Le bon côté de Xcode c'est qu'il vous propose, au fur et à mesure que vous tapez votre code, des complétions de texte assez efficaces.

En effet, quand on code sur Apple, on utilise surtout les API fournies par Apple, c'est-à-dire des méthodes et fonctions utilisées par OS X ou iOS et par toutes les autres apps natives Apple.

Pour afficher une fenêtre, on utilise la classe NSWindow sur OS X, qui contient plein de méthodes destinées à manipuler des fenêtres OS X : changer la taille, l'arrière plan, etc. Un champ de texte sera un NSTextField, et on sait déjà -car documenté par Apple- ce que ces méthodes font et comment elles le font. On verra ça plus en détail ensuite.

Donc il faut utiliser des milliers de méthodes contenues dans des milliers de classes, certaines valides pour toutes les versions d'OS X, certaines uniquement valides pour des versions spécifiques, d'autres sont encore compatibles avec le tout dernier OS mais Apple informe qu'il ne faut plus les utiliser ensuite, etc.

Il est impossible de tout mémoriser, évidemment. On se fait donc aider par Xcode qui propose lui-même les bonnes méthodes à utiliser au fur et à mesure qu'on tape (car il analyse tout le temps ce que l'on fait, en contexte avec ce qui a déjà été fait).

On tire également avantage des conventions créées par Apple : par exemple, il y a toujours une logique derrière les noms de classes et de méthodes, et cette logique se retrouve dans leurs noms, qui sont donc assez verbeux.

Xcode propose aussi une aide assez fournie, toute la documentation est très riche et accessible en contexte dans le code ; seul problème, à l'heure où j'écris cet article la majorité de l'aide pour Swift est encore ténue, alors que cela foisonne d'infos pour Objective-C, bien sûr.

Principes de base

La majorité des apps ont une interface graphique (“GUI”), et la notre sera simple : un champ de texte pour entrer le contenu à encoder/décoder, un bouton sélecteur encoder/décoder, et un champ de texte pour afficher le résultat (pas de bouton d'action, on déclenche en tapant ENTER).

Chacun de ces éléments graphiques est relié à notre code par l'intermédiaire d'Xcode.

Le principe vu ici c'est d'utiliser “IB”, “Interface Builder”, la partie de Xcode qui permet de créer une interface graphique.

On y pioche des objets tels que des champs de texte, des frames pour images, des cellules contenant des éléments, des tables, et bien entendu des fenêtres, menus et autres boutons.

On les dépose sur le workspace qui représente la partie visible de l'application, on en règle la taille, l'emplacement, les attributs, les identifiants, etc.

Ensuite on les “relie” au code.

Il y a plusieurs mécanismes, le plus simple étant de passer Xcode en mode “Assistant” (icone des deux cercles entrelacés), de faire CTRL+CLIC sur l'objet puis de tirer le trait qui vient d'apparaître vers le fichier de code dans lequel vous voulez insérer le lien.

On choisit alors si l'élément inséré est une action (“IBAction”) ou la représentation d'un objet (“IBOutlet”), et on choisit de quelle classe il hérite (la classe générique NSObject par défaut).

On manipule ensuite ces variables ou constantes qui représentent les objets de l'interface et qui contiennent les méthodes prescrites par Apple à cet effet.

Il y a beaucoup à dire à ce sujet, mais ce n'est pas l'objet de cet article, donc je reste évasif et concis volontairement.

Evidemment je ne vais pas me lancer ici dans une explication des principes de base de Cocoa, la bibliothèque d'objets et fonctions pour créer des apps OS X.

C'est une bête énorme, et ça prend des mois - voire des années - à comprendre, apprendre et maîtriser. D'ailleurs je n'en suis personnellement qu'au début de l'aventure, je me verrai donc mal faire le prof là-dessus.

On va simplement découvrir au fur et à mesure ce dont on a besoin pour notre app.

Quant à Swift, il faut simplement garder à l'esprit pour cet exercice que c'est un langage fortement typé, c'est-à-dire que le type des objets (string, integer, float, array, dictionary, struct, etc) doit être explicite la plupart du temps.

Ca signifie que ça, possible en Ruby:

# Ruby
truc = "yolo"
truc = 12

n'est pas possible en Swift : on ne peut pas changer le type d'une variable en cours de route.

C'est soit une chaîne de caractères, soit un nombre, soit autre chose, mais une fois typée, une variable doit garder ce type.

// Swift
var truc = "yolo"
truc = 12   // erreur

Swift est souvent capable de trouver lui-même le type d'une variable (string, integer, array, etc), c'est “l'inférence” (cependant, parfois, il vaut mieux indiquer manuellement le type pour faciliter le travail du compilateur).

// chaîne de caractères
var mot = "yolo" // type: String
// nombre entier
var nombre = 12 // type: Int
// tableau (Array) de chaînes de caractères
var tableau1 = ["yolo", "ok", "nope"] // type: [String]
// tableau de nombres entiers
var tableau2 = [12, 33, 0] // type: [Int]
// dictionnaire avec integer pour clé et string pour valeur
var dico = [12: "yolo", 33: "ok", 0: "nope"] // type: [Int:String]

Attention à ne pas confondre String, Dictionary, Array, etc qui sont des mots réservés de Swift, avec NSString, NSDictionary, NSArray, etc qui sont des équivalents mais spécifiques à la bibliothèque Cocoa.

Bon, pas de panique, encore une fois on n'étudiera que ce qui nous concerne ici.

GO !

Commencez donc, si ce n'est déjà fait, par mettre à jour votre machine.

OS X, Xcode : faites une belle update. Swift et les outils qui l'accompagnent évoluent rapidement, on évite de nombreux problèmes en étant bien à jour.

Ouvrez Xcode, et faites “Nouveau projet”. Choisissez “OS X”, “Application Cocoa”, langage “Swift”, pas de Core Data, pas de Storyboard.

Une fois que Xcode aura ouvert les templates, vérifiez que tout soit correct en lançant une compilation de cette application vide. Si ça ne marche pas, vous devrez commencer par régler ces problèmes avant de commencer à suivre ce tutoriel.

Pour lancer la compilation, cliquez sur l'icone “Play” en haut à gauche dans la barre d'outils.

Une petite fenêtre vide, avec une icone générique dans le Dock, devrait surgir au bout de quelques secondes.

C'est le template de base, proposé par Xcode, pour une app OS X.

Vous pouvez ensuite cliquer sur l'icone “Stop”, juste à côté de “Play”, pour la quitter, ou le faire en passant par son propre menu.

GUI

Objets de l'interface

On va commencer par créer un outlet fenêtre avec NSWindow, puis d'aller piocher dans le sélecteur d'objets de Xcode des NSTextField et NSPopUpButton, les déposer sur la fenêtre et les relier à notre fichier AppDelegate déjà fourni par Xcode lors de la création du projet.

En plus des “outlets” il faut créer deux actions, une pour le sélecteur encode/décode et une pour l'appui sur ENTER dans le champ de texte.

En faisant CTRL+CLIC sur le sélecteur dans Interface Builder, tirez vers AppDelegate et créez non pas un “outlet” mais une “action”. Pareil pour le champ de texte.

Nous étudierons le contenu de ces deux actions un peu plus loin, pour l'instant on les laisse vides.

Apparence

Notre fenêtre et ses éléments peuvent être fixes, ou être soumis à des contraintes de mise en page (layout) : par exemple, on peut vouloir que les éléments s'adaptent lorsqu'on redimensionne la fenêtre, ou au contraire les laisser là où ils sont sans interagir avec cette action de l'utilisateur.

Toujours pour des questions de simplicité, on n'évoquera pas ça dans cet article, c'est un domaine très vaste et assez complexe.

Dans notre cas, on va se contenter de mettre en place quelques automatismes pour le redimensionnement de la fenêtre.

On décoche simplement la case “AutoLayout” :

Et optionnellemnt comme je l'ai fait vous pouvez placer des contraintes de redimensionnement aux objets pour qu'ils restent collés aux bords de la fenêtre :

Organisation

AppDelegate est le fichier qui contient le code qui parle directement à l'application visible.

Il y aura aussi des fichiers dédiés à des classes, qui seront utilisées par ce “délégué de l'application”.

Dans notre cas, une classe dans un fichier dédié à encodage/décodage NATO, et une autre classe dans un autre fichier pour gérer les conversions mots/caractères.

Nous avons déjà fait l'interface graphique, et avant de nous occuper de cet AppDelegate nous allons créer les deux classes, “Nato.swift” et “ConvertText.swift”, et leurs fonctions.

NATO

L'idée est toute simple : on a un dictionnaire de valeurs lettre/mot pour l'encodage et un dictionnaire de valeurs mots/lettres pour le décodage.

Si je demande la valeur du dictionnaire encode pour le caractère a j'obtiens le mot alpha, si je demande b j'obtiens bravo, etc.

Ca marche dans l'autre sens avec le dictionnaire decode : je demande alpha et j'obtiens a, ainsi de suite.

En théorie on aurait pu ne faire qu'un seul dictionnaire et l'inverser par une méthode lors d'un des deux appels, mais dans notre cas on pourrait vouloir déclarer certaines exceptions, pour la ponctuation par exemple, donc le dictionnaire ne serait pas toujours forcément symétrique.

Faites “New” dans le menu “File” de Xcode, et créez un nouveau simple fichier Swift nommé “Nato.swift”.

On ajoute l'import de la bibliothèque standard “Cocoa” au début du fichier pour bénéficier des méthodes fournies par Apple.

Dans notre classe on va créer deux dictionnaires clé/valeur : encode pour l'encodage, decode pour le décodage, et un tableau preserved pour les exceptions.

On va également créer deux fonctions, wordsToLetters et lettersToNato, qui chacune vont obtenir une valeur en résultat et la renvoyer à qui les appelle, c'est-à-dire nous dans notre AppDelegate.

let encode = ["a": "alpha", "b": "bravo"]
let decode = ["alpha": "a", "bravo": "b"]

Decode

Pour décoder du texte NATO, comme par exemple hotel echo lima lima oscar, on va itérer dans un tableau de mots, ceux entrés par l'utilisateur, et pour chaque mot demander au dictionnaire decode quelle est la lettre qui correspond.

On va pour ça créer une fonction qui prend un tableau de chaînes de caractères en entrée et qui renvoie un tableau des lettres qui formeront la phrase finale.

func wordsToLetters(#nato: [String]) -> [String] {
    ...
}

Dans cette fonction, on crée un tableau pour stocker le résultat :

var res = [String]()

Puis on fait une boucle qui itère dans le tableau de mots reçu en arguments (l'objet du tableau nato sera temporairement nommé ici word à chaque tour de boucle) :

for word in nato {
    ...
}

Dans cette boucle, donc pour chaque objet du tableau, on demande de stocker dans une constante temporaire char le résultat de la question “Quel est la lettre dans le tableau decode qui correspond au mot word ?” :

if let char = decode[word] {
    // faire ici quelque chose avec `char`
}

Il n'y a pas de else, donc si rien ne correspond, la condition est ignorée et la boucle reprend au prochain item.

S'il y a correspondance, on prend le résultat, un caractère, et on le met dans le tableau temporaire nommé res :

res.append(char)

Puis la fonction retourne le résultat.

Encode

Nous créons une fonction lettersToNato qui prend un tableau de caractères en argument (argument nommé letters) et qui retourne un tableau de mots (donc de chaînes de caractères) :

func lettersToNato(#letters: [String]) -> [String] { ... }

On crée un tableau vide pour le résultat à venir (de type mots), et on commence une boucle qui va itérer dans le tableau de lettres que l'on a reçu en argument :

var bogies = [String]()
for letter in letters {
    ...
}

On va créer dans cette boucle, à chaque itération, deux possibilités : s'il existe un mot qui correspond à la lettre, ou pas.

Si oui, on le stocke dans le tableau de résultats.

Si non, on teste pour voir si la lettre fait partie de la liste des exceptions.

La condition, et l'action en cas de “succès” :

if let word = encode[letter] {
    bogies.append(word)
}

Si la clé letter dans le tableau encode correspond à un résultat, alors ce résultat est stocké temporairement dans word et nous stockons alors nous-mêmes cette donnée dans le tableau de résultat (nommé bogies).

Les deux branches de la condition sont bien entendu reliées par un else (qui signifie “ou sinon”).

La deuxième branche, pour le cas où aucun mot n'est trouvé pour la lettre :

for pre in preserved {
    if letter == pre {
        bogies.append(letter)
        break
    }
}

Ca n'avait rien d'obligatoire, mais j'ai préféré considérer le point d'exclamation et le point d'interrogation comme pouvant faire partie de la phrase finale.

Il faut donc tester la lettre pour laquelle aucun mot n'a été trouvé pour voir si elle correspond à une des exceptions. On boucle donc dans le tableau preserved et si la lettre correspond on stocke le résultat dans bogies puis on break, c'est-à-dire qu'on n'a pas besoin de continuer à parcourir le tableau preserved, on peut faire un jump vers une nouvelle itération de la boucle supérieure (for letter in letters).

Puis on retourne le résultat de la fonction.

Conversions de texte

Pour manipuler toutes ces informations nous créons une classe “ConvertText.swift” qui aura pour rôle de convertir des chaînes de caractères en tableaux de caractères, convertir du texte vers un tableau de mots, et convertir un tableau de mots vers un tableau de caractères.

Texte vers mots

La fonction textToWords pour commencer : on prend un texte en entrée et on retourne un tableau de mots.

func textToWords(#text: String) -> [String] { ... }

On va découper le texte à chaque caractère “espace” recontré pour faire des mots.

Pour cela on crée d'abord une version toute en minuscules du texte en appellant la méthode de Swift lowercaseString sur la variable text reçue en argument de la fonction, et on stocke ça dans la constante nommée down :

let down = text.lowercaseString

Ici, pour rester simple, ce découpage se fait avec la méthode componentsSeparatedByString de Cocoa.

let bucket = down.componentsSeparatedByString(" ")

Mots vers caractères

Après avoir créé un tableau de caractères nommé letters pour stocker le résultat, on boucle dans le tableau de mots words reçu en argument de la fonction.

Dans cette boucle, on crée une nouvelle boucle qui pour chaque mot va itérer sur chaque caractère, et stocker le caractère dans le tableau letters.

Après cette boucle de niveau 2, toujours dans la boucle de niveau 1, on ajoute un espace au tableau de lettres. En effet il faut séparer les mots par des espaces si on veut que la phrase soit lisible…

Ensuite, on sort de la boucle de niveau 1 et on verifie si le tableau letters a été rempli : si oui, on en supprime le dernier élément (qui est le dernier espace ajouté après le dernier mot).

Et on renvoie le tableau de caractères à l'appellant.

Texte vers caractères

Nous allons utiliser les deux fonctions que nous venons de créer pour obtenir le résultat de cette fonction.

On reçoit en entrée un texte (une suite de mots), et l'on renvoie un tableau de caractères.

On va donc découper la suite de mots avec textToWords, renvoyer ce résultat à wordsToChars, et retourner le tout.

Et bien entendu je retourne le tout directement sans le stocker d'abord, juste pour me contredire et montrer que c'est possible quand même. ;)


Mise-à-jour Swift 2

PARAMÈTRES

A partir de Swift 2 (apparu dans Xcode 7 beta), les # précédant le premier paramètre d'une fonction, qui signifiaient “le paramètre est obligatoire”, ne sont plus disponibles.

Ils étaient dans notre exemple à titre indicatif, et nous pouvons donc sans souci nous conformer à cette règle en supprimant les # de la déclaration et en adaptant les appels :

func textToChars(text: String) -> [String] {
    return wordsToChars(textToWords(text))
}

func textToWords(text: String) -> [String] {
    let down = text.lowercaseString
    let bucket = down.componentsSeparatedByString(" ")
    return bucket
}

func wordsToChars(words: [String] ) -> [String] {
    ...
}

Je vous laisse adapter vous-même le reste des # et leurs appels de ce tuto, vous verrez qu'en plus Xcode vous prend par la main avec son assistant d'aide automatique.

STRINGS

D'autre part, avec Swift 2, désormais le type String n'est plus conforme au protocole SequenceType.

Ce qui signifie concrètement que nous ne pouvons plus itérer directement, caractère par caractère, dans une chaîne de caractères, comme nous le faisions.

A la place, nous devons spécifier explicitement que nous souhaitons obtenir la représentation par caractères de la chaîne.

Dans notre méthode wordsToChars, ça donne :

for char in word.characters {
    letters.append("\(char)")
}

AppDelegate

C'est ici que nous allons utiliser tout ce que nous avons construit dans Nato.swift, ConvertText.swift et l'interface graphique.

AppDelegate est le délégué de l'application, c'est là que nous allons assembler tous les éléments et les interconnecter.

Nous n'allons pas étudier en détail tous les éléments, cela nous conduirait à écrire un livre et non pas un tuto… mais nous allons essayer tout de même de couvrir quelques bases.

Initialisation

Dans ce fichier AppDelegate il y a une méthode applicationDidFinishLaunching déjà préparée par Apple.

Cette méthode exécute son contenu, comme vous l'avez deviné, juste au moment où l'application vient de se lancer et est prête à travailler.

On va utiliser cet emplacement pour déclarer quelques valeurs et attributs.

Par exemple, on se crée une couleur custom :

let darkBlue: NSColor = NSColor(red:0, green:0.29, blue:0.56, alpha:1)

La variable darkBlue contient désormais “notre” bleu, que nous avons créé avec la classe NSColor. Cette classe contient également des couleurs prédéfinies par Apple.

Donnons quelques couleurs à nos éléments :

title.textColor = NSColor.whiteColor()
textIN.textColor = NSColor.whiteColor()
textOUT.textColor = NSColor.whiteColor()
window.backgroundColor = NSColor.whiteColor()
title.backgroundColor = darkBlue
textIN.backgroundColor = darkBlue
textOUT.backgroundColor = darkBlue

Ici, textIN est le “IBOutlet” créé lorsque l'on a fait CTRL+CLIC sur le premier champ NSTextField dans l'éditeur d'interface graphique et que l'on a tiré vers le fichier de code.

Cette variable représente l'objet et permet d'accéder à ses méthodes (fournies par Cocoa, donc), comme textColor, backgroundColor, etc.

Tapez juste textIN. + TAB dans Xcode et vous allez voir apparaître toutes les méthodes disponibles pour cet objet.

Pareil pour window ou tout autre objet dans le code :

On “nettoie” le bouton sélecteur des éléments fournis par défaut avant de lui donner les deux attributs qui nous intéressent :

popUpButton.removeAllItems()
popUpButton.addItemsWithTitles(["Encode", "Decode"])

On demande à la fenêtre de se placer au centre de l'écran :

window.center()

Ici, window est le IBOutlet de notre fenêtre, et center est une des méthodes fournies par Cocoa.

On déclare également à OS X que notre premier champ de texte sera éditable par l'utilisateur :

textIN.editable = true

Voilà, l'essentiel est fait pour l'initialisation de notre app.

Ah oui, hors cette méthode applicationDidFinishLaunching nous avons aussi déclaré un booléen, c'est-à-dire un flag dont l'état est true ou false.

var encoder = true

C'est juste pour définir un état par défaut du bouton sélecteur disponible au lancement de l'application.

ACTIONS

Sélecteur

Lorsque nous avons créé notre méthode pour l'action du sélecteur, Xcode a lui-même formaté la fonction avec un argument, sender :

@IBAction func chooseAction(sender: NSPopUpButton)

C'est cette variable sender qui contient la valeur du bouton sélecteur, et que l'on va stocker dans une constante temporaire à l'éxécution d'une action sur ce sélecteur :

let action = sender.titleOfSelectedItem!

Ensuite on vérifie simplement si ce texte est égal à l'un ou à l'autre des contenus que nous avons créé plus tôt pour le bouton avec

popUpButton.addItemsWithTitles(["Encode", "Decode"])

en faisant un simple if qui modifiera la valeur du booléen encoder :

if action == "Encode" {
    encoder = true
} else {
    encoder = false
}

Ainsi, à chaque fois que l'utilisateur change la valeur du sélecteur entre “Encode” et “Decode”, ce choix est reporté dans le booléen encoder et permettra d'exécuter l'une ou l'autre action.

Champ de texte

Lorsque nous avons fait CTRL+CLIC et tiré le premier NSTextField vers le fichier AppDelegate, Xcode a créé cette action :

@IBAction func editField(sender: NSTextField) { ... }

Cela signifie que le contenu de cette méthode sera exécuté à chaque action du champ de texte, c'est-à-dire à chaque fois que l'utilisateur appuie sur ENTER pour en valider le contenu.

A chaque fois que l'on fait ENTER, la valeur du champ de texte change et se retrouve automatiquement dans son attribut stringValue, et l'on stocke cette valeur dans une constante content :

let content = textIN.stringValue

On va initialiser des instances de nos deux classes créées dans “Nato.swift” et “ConvertText.swift” :

let convert = ConvertText()
let nato = Nato()

Dans une condition à deux branches, une pour la situation “encode” et l'autre pour la situation “décode”, on va utiliser ces deux objets pour faire le travail sur le texte.

Pour encoder, on transforme donc d'abord le texte entré en un tableau de caractères, et chaque caractère dans ce tableau est ensuite transformé en mot puis le tout est stocké dans un tableau de mots :

let letters = convert.textToChars(text: content)
let natoWords = nato.lettersToNato(letters: letters)
response = join(" ", natoWords)

Comme notre méthode textToChars dans la classe ConvertText retourne explicitement un tableau de caractères, on n'a pas besoin de déclarer le type de la constante letters ici, c'est le compilateur qui le fait lui-même.

Pareil pour natoWords, Xcode sait déjà que le résultat sera un tableau de mots comme déclaré explicitement dans la méthode dans le fichier “Nato.swift”.

On rassemble ensuite tous les mots que l'on relie par un espace et on le stocke dans la variable response de type String, déjà préparée plus haut.

C'est le même principe mais dans l'autre sens pour l'action “Decode” :

let words = convert.textToWords(text: content)
let letters = nato.wordsToLetters(nato: words)
response = join("", letters)

Chaque mot NATO est découpé du texte fourni, puis converti en lettres, finalement reliées sans espace (on forme ici des mots).


Mise-à-jour Swift 2

A partir de Swift 2 la plupart des fonctions globales de type join sont devenues des méthodes accessibles sur l'objet lui-même.

Dans notre fonction editField nous devons donc remplacer les appels à join comme ceci, respectivement :

response = " ".join(natoWords)

et

response = "".join(letters)

Il ne reste plus qu'à attribuer le résultat à la variable stringValue du champ de texte pour que ce soit visible immédiatement dans l'application :

textOUT.stringValue = response

En disant au AppDelegate que la valeur de le chaîne de caractères du champ de texte a changé, le délégué va lui-même immédiatement refléter ce fait dans l'application elle-même à l'écran.

Voilà, c'est fait ! Compilez, l'application fonctionne !

Vous pouvez la récupérer comme ceci :

L'application n'est pas distribuable dans l'App Store en l'état, on n'a pas vu les icones, les réglages débug/optimisation, les éventuels scripts d'installation, etc, mais le fichier .app est néanmoins parfaitement utilisable et copiable d'une machine à l'autre.

Conclusion

Nous n'avons fait qu'éffleurer le sujet mais pourtant il a déjà fallu aborder une tonne d'informations.

Il y a encore beaucoup à dire : sur Swift, sur Cocoa, sur Xcode… et nous en reparlerons dans de prochains épisodes. :)

Les fichiers Swift 2 sont disponibles sur GitHub.

Auteur: Eric Dejonckheere