Sommaire
Coucou 🙂,
Après un article un peu plus léger de la semaine dernière, cette semaine, on va dans quelque chose de plus dur...
Ce n'est pas ma première application sur l'App Store, que ce soit des projets perso ou encore des applications pour des clients, j'ai déjà eu l'occasion de travailler sur iOS, au travers de Cordova à l'époque ou encore React Native, mais j'ai mis un peu cette partie de côté...
Dans le cas d'iOS quand on veut faire du native, il est nécessaire d'utiliser l'IDE XCode, et le langage de prédilection, en 2023, est le Swift.
En fait, je n'ai rien contre ces environnements, c'est très agréable a utiliser, Apple met des moyens pour donner aux développeurs l'envie de créer sur leur plateforme, c'est un fait.
Mais je n'ai jamais approfondi mes connaissances sur cet environnement...
Pourquoi ?
Le Swift, ne peut être utilise qu'a une seule chose (même si on peut l'exécuter sur des serveurs), travailler dans un écosystème, celui d'Apple, cet écosystème est très verrouillé, et de mon expérience, la validation de l'application sur l'App Store (le "side loading" n'est pas une option dans le monde pro) et très stricte.
Apple a des règles très strictes, et, si vous voulez exister sur l'iPhone, vous devez vous y plier, c'est tout.
C'est pourquoi, je n'avais pas envie d'investir du temps dans des compétences que je ne peux utiliser que sous le giro d'Apple, je préfère approfondir mes connaissances dans des technologies qui me permette d'offrir des services a plus de monde...
Sur ces technologies-là, j'ai passé pas mal de temps, avec par exemple cette application, qui fonctionne avec Ionic Framework (Angular)
Tout comme le « 100% native », j'ai mis de côté cette technologie... cela faisait presque 4 ans que je n'ai plus écrit de code pour une appli qui finirait sur l'App Store.
Pourquoi ?
En fait, je trouve que c'est une utopie, en gros, la "webview", fait tout, il y a des plugins, qui eux sont écrits en code natif, mais, dès qu'on veut faire des trucs un peu plus poussés, on a l'impression qu'on fait du bricolage... par exemple, j'ai souvenir d'une application dans laquelle l'utilisateur pouvait se connecter facilement, en scannant un QR code affiché sur son ordinateur
Et pour afficher ce que la caméra du téléphone filme, je devais mettre le fond de ma vue (HTML) en transparent, de sorte que la « vue native » soit affichée.
Cela n'est qu'un exemple parmi tant d'autres, mais à chaque fois que je fais ce genre de chose je me dis "Ah oui, c'est du bricolage de haut niveau là quand même..."
Il y a un autre point qui me bloque, pourquoi utiliser le web pour faire les transitions ? iOS a son propre contrôleur de navigation, et il fonctionne parfaitement, il gère les transitions et donne accès aux gestes que les utilisateurs ont l'habitude d'utiliser, ça serait bien de les utiliser non ?
J'ai vu par la suite que d'autres ont eu la même idée, et que l'on commençait à voir des plugins permettant de remplacer les transitions CSS de Ionic avec celle de l'OS, mais à ce moment-là je ne m'intéressais plus vraiment à ça, car Cordova était en train de gentiment se retirer et remplacé par « Capacitor »
En fait dans le cas de React Native, je lui reproche la même chose que je reproche a React, c'est une technologie, comme le natif, qui demande un investissement énorme, et on voit beaucoup d'entreprises courir après des profils de développeur « React » et « React Native », car elles ont commencé a travailler avec ces technologies (car c'est ce que les développeurs de l'époque voulaient) et son maintenant coincé dans un écosystème complexe, avec peu de profils sur le marché (les lois de l'offre et la demande, tout ça).
J'ai d'ailleurs, en mission freelance, dépanné une entreprise qui a été dans cette situation (je n’ai pas dit que je ne connaissais pas React, juste que quand je peux l'éviter, je l'évite)
Sur ce, revenons à Turbo Native, si vous le voulez bien 😁
Comme toujours, mon application "pilote" utilisée en production sur Symfony-UX/Turbo, et a l'heure où j'écris ces lignes, elle est bien diffusée sur l'App Store.
En fait, quand l'an dernier j'ai travaillé sur cette application, je prévoyais déjà son arrivé sur l'App Store, en faisait mes recherches sur « hotwired Turbo », j'ai vu que "Strada (en bas de page)" été en cours de développement, à l'époque, même si des versions "beta" était dispo, j'ai juste gardé l'info dans un coin de ma tête.
En fait, à cette époque, je n'avais pas du tout regardé les travaux réalisés par l'équipe de Turbo sur « Strada », je n'avais que mes connaissances sur ce qu'Apple attend de toi quand tu veux publier une application sur l'App Store.
J'aime beaucoup Turbo, mais, de base, il y a un truc qui me dérange, c'est que quand on clique sur quelque chose, qu'on valide un formulaire, il y a un moment de "flottement", on a l'impression que l'application est bloquée, même si c'est moins de 40ms, il y a pas de "satisfaction" que l'action a bien été prise en compte.
Du coup, sur Vélo en France, j'ai imaginé, ma version de Turbo, une version qui, quoi qu'il arrive, donne a l'utilisateur de l'information sur l'état de l'application, vous pouvez essayez, cliquez sur un bouton, vous aurez toujours un « spinner » qui vous informe que "ça charge", du coup même si vous avez une connexion pas folle, vous avez toujours une sensation de fluidité.
Sur l'application de réservation, il y a également ce « spinner », mais, ce n'est pas poussé à ce point, je n'ai pas directement modifié Turbo pour le faire, j'utilise des "Turbo Frame" d'ailleurs ça, ça va me poser problème plus tard
Et pourquoi je me prends autant la tête avec ces transitions ?
C'est très simple, essayez de mettre une application sans transition propre sur l'App Store, vous serez tout simplement rejeté...
"On ne veut pas un site internet, on veut une application"
Le truc, c'est que j'ai anticipé un problème sur lequel je n'étais pas le seul a travaillé, dans le cas de « Turbo Native » sur iOS les transitions sont gérrer... on y revient plus tard 😁.
Contrairement a une application "classique" ou le serveur renvoie juste du JSON, et, toute partie graphique est traitée par la partie client, « Turbo » part du principe que nous utilisons le moteur du « templating », qu'il y a coté serveur...
Et alors ?
Lors de la vérification de l'application, les équipes d'Apple se situent... en Californie, et les serveurs qui « créent » les vues ? En France.
Du coup, pour résumé, quand ils testent l'application, leurs requêtes traversent les États-Unis d'est en ouest et hum... un océan 🤣, niveau latence, on est bon là non ?
Le truc c'est que le réseau, je n'ai pas la main dessus, moi je contrôle le client et le serveur, ce qui se passe entre, hormis mes yeux pour pleurer, je n'ai rien.
Coté serveur, il faut répondre le plus vite possible, pour ça il faut un code optimisé, on a du PHP qui exploite a fond OPCache, « Redis » et ce genre de technologie ou encore... « Varnish » 🙂.
Coté client, Turbo a déjà un système de snapshot... et si on le modifiait pour que quand un utilisateur lance l'application, on mette déjà en cache certaines choses ? 😮
Voilà en gros, les sujets de réflexion que j'ai eue, avant de voir le travail réalisé par l'équipe Hotwired sur « Turbo native ». 🙂
Nous y voilà, Strada est là, ce tweet l'annonce officiellement. En effet, en regardant la page github, on voit la « release » 7.0.0.
Bon bon bon, regardons tout ça, déjà en regardant la doc, il y est écrit qu'il faut créer un projet "xcode", ah, ça, m'intéresse ça veut dire qu'on a le contrôle total sur le code natif...
Après une heure d'exploration de tout ça, un message sur What's app est partie 😮
Comme vous vous en doutez, la réponse a été oui
Dans un premier temps, il va falloir donner la possibilité à l'application iOS de communiquer avec Turbo pour cela c'est très simple :
Eh oui, ultra simple, on rend Turbo accessible dans le « window ».
Pour ça, dans Symfony, nous allons créer un service, qui regarde si dans l'user Agent il y a des traces de « Turbo Native » :
Vous devriez également créer une fonction Twig qui utilise, pour savoir quand le client est l'application iOS.
Pensez également au front, à peu près le même code, mais en JavaScript :
Par défaut, Turbo Native n'ajoute rien à l'user comme agent, nous allons le faire dans le chapitre "cas concret" de cet article, plus bas.
Allez, maintenant, on bosse.
Vous vous souvenez plus haut ? Quand je vous parlais des applications « 100% natives » ?
Je vous disais ne pas vouloir passer de temps dans des technologies qui ne fonctionne que dans l'écosystème Apple, mais là, c'est différent, je n'ai pas besoin de savoir-faire des interfaces avec le système de « vue » propriétaire d'Apple, en fait il faut juste comprendre quelques bases, cela ma pris en tout une 30aines d'heures.
Bon, pour le coup là, « Turbo native » seul, il est un peu limite, du coup, si vous voulez vous y mettre, je vais vous faire gagner du temps, vous allez avoir besoin de ce projet : « Turbo navigator ».
Pour faire simple, « Turbo navigator », inclus le code que vous devriez avoir dans toutes vos applications « Turbo Native », utilisez, modifiez-le, adaptez-le, d'ailleurs, vous pouvez voir une version "simplifié", dans l'application de démo
Les WKWebview, sont des composants d'interface pour application native iOS, c'est comme leurs noms l'appliquent, des « vues » utilisant le moteur de Safari, mais attention, ce n'est pas Safari, par exemple la fonction "window.navigator.share" n'existe pas. Il va falloir la réécrire 😮 on voit ce genre plus en détail plus bas.
Ce qu'il qu'il faut comprendre ici, c'est que, contrairement a une application Cordova, vous n'avez pas forcément qu'une « webview », par exemple, quand vous souhaitez ouvrir un modal, vous allez charger une seconde webview, il faut faire attention a ne pas en abuser, car chaque webview, c'est un peu de mémoire en plus. mais surtout, de base, elles ont chacun leurs cookies, leurs locaux storage... etc.
Pour palier a ça, on utilise un "WKProcessPool", qui va nous permettre de partager les cookies, le local storage, entre différentes views.
Dans certains cas, vous allez devoir envoyer des informations qui seront traitées par le code natif, pour cela, les WKWebview exposent des fonctions cotées JavaScript, cette fonction c'est :
à noté ici que "nativeApp" est une variable que vous choisissez dans le code natif.
Depuis le code natif, vous pouvez communiquer avec le code JavaScript, pour cela, il faut utiliser la fonction :
Vous l'aurez compris, en fait on « injecte » du JavaScript, comme si on le faisait depuis la console des navigateurs.
Ici, je n'entre pas trop dans les détails, simplement car dans le prochain chapitre, on va prendre de vrai cas concret, avec de vrais bouts de code utilisé en production, je veux simplement vous donner les bases : En plus de ça, vous pouvez jetez un œil au projet sur Github et a sa documentation, mais surtout au blog de Joe Masilotti, qui est le contributeur principal de « Turbo Native », sur son blog, vous trouverez énormément de ressources pour allez plus loin.
Maintenant, on va prendre des cas concrets...
Dans le paragraphe précédent, on a survolé les choses à savoir, ça sert un peu a rien d'aller dans le détail sur des choses qui sont déjà très bien documenter ailleurs, ici on va prendre des cas concret et intéressant, dans la mesure du possible, lisez ces différents chapitres dans l'ordre, car, comme vous le verrez des choses reviennent et je ne reviendrais pas au détail a chaque fois
On en a parlé précédemment, coté serveur (et dans le JavaScript) on a besoin de détecter quand l'application client est Turbo Native iOS, pour cela, on va ajouter du texte dans l'user agent, plus précisément « Turbo Native iOS », ces trois mots nous permettent de savoir si on utilise Turbo Native et si oui, si c'est la version iOS.
Pour cela, ça se passe dans le code natif, mais vous avez de la chance, c'est déjà fait dans Turbo Navigator (je vous ai dit que c'était un indispensable 😛)
Regardez, cela se passe dans ce fichier, en haut on voit la définition de la « string » que l'on va ajouter à « l'user agent ».
Puis, plus bas : « configuration.applicationNameForUserAgent = userAgent »
L'objet « WKWebViewConfiguration » sera passé à la « WKWebview », de ce fait, elle ajoutera « Turbo Native iOS » à l'user agent utilisé pour chaque requête.
Et vous allez voir, on l'utilise "tout le temps"
Vu que nous sommes sous iOS, quand l'utilisateur souhaite connaitre un itinéraire (dans notre cas, allez au restaurant) on va préférer ouvrir l'application Apple plan, qui est sur tous les iPhone.
Dans la documentation d'IOS, il est indiqué que chaque lien passé à l'os contenant "https://maps.apple.com/" sera ouvert dans l'application plan.
Du coup, vous vous dites surement "eh bien, il suffit de faire un lien dans HTML vers plan, oui, mais non, rappelez-vous, nous sommes dans « WKWebView » pas dans « Safari », si on fait ça alors, ça ouvrira le lien dans le "Safari embarqué" proposé par iOS, et ce n'est pas ce qu'on veut, nous, on veut que plan s'ouvre directement.
Pour commencer dans notre vue "twig" ;
Comme vous pouvez le voir, ici, on dirait un simple lien, mais en fait non, ce lien va être intercepté, c'est aussi du code que l'on retrouve dans « Turbo Navigator », plus précisément ici
Du coup, il faut modifier la fonction, détecter si on cherche à aller sur Apple Plan et le cas echant passer ça directement a iOS
Ici vous reconnaissez la fonction, d'origine de Turbo Navigator, juste un if devant, si on détecte map, on envoie le lien directement à iOS.
On pourrait passer par un contrôleur Stimulus et pousser du code de la webvieww vers le code natif pour ça, mais ça compliquerais pour pas grand-chose...
Vous pouvez voir ce que ça donne sur cette vidéo (Youtube)
On continue, cela reste très similaire a ce que l'on a vu précédemment, mais là, ça va être un peu plus compliqué, en effet, si vous tenter de mettre un lien "tel" dans votre application, cette fois, cela fera carrément "crash" votre application, WKWebview ne supporte que les liens HTTP et HTTPS par défaut
Dans notre vue "twig" :
Comme vous pouvez le voir ici, on fait appel à un contrôleur Stimulus, et on intercepte le "click" sur notre lien, voici le contrôleur « Stimulus » :
Ici, on utilise la classe que je vous ai montrée plus haut pour détecter si nous sommes dans une application Turbo native, ou non.
Si nous le sommes alors nous faisons appel a la fonction exposé par « WKWebView », "webkit.messageHandlers.*.postMessage" nous permettant d'envoyer des informations au code natif.
Maintenant côté natif, il va falloir gérer cela :
Aïe, là, c'est déjà un peu plus compliqué, en fait, en Swift, on a des classes parentes, conçu pour écouter des événements, ici notre classe parente est "WKScriptMessageHandler"
Comme au-dessus, cliquez ici pour voir la vidéo de ce que ça donne (sur YouTube)
Dans une application, si vous êtes observateur vous avez remarqué qu'il y a des animations entre chaque « view », de plus il y a tout un système de gestuelle que les utilisateurs de la plateforme, par exemple glisser de la gauche vers la droite pour retourner en arrière (comme si on enlevait la dernière vue de la pille).
De base Tuurbo Native, suit simplement les liens qui sont gérés par le JavaScript, après, dans la version "de base" c'est assez limité je suis donc passé assez rapidement a Turbo Navigator, qui gère vraiment très bien ça. quand le JavaScript fait un "visit" alors le contrôleur de navigation iOS ajoute une nouvelle vue a la pile sauf si cela se passe dans une « turbo frame ».
C'est amusant, déjà dans mon premier article sur Symfony-UX/Turbo j'en parlais, avec Stripe j'ai encore eu un petit souci...
En fait, Stripe élément, une fois le process de création de « setupIntent » terminé, va utiliser un « window.location » pour charger la « page de destination », et ça dans turbo native par défaut ça ouvrirait Safari et, si vous utilisez la surcouche Turbo Navigator, ça ouvrirait un Safari embarqué « in app ».
Du coup, le mieux est de dire a Stripe de ne pas recharger la page, et faire nous-mêmes un "Turbo.visit" :
Et là plus de problème, bon, ici, je donne Stripe comme exemple, mais en fait, dans l'app de réservation, Stripe n'est pas une « pile de navigation » traditionel.
Lors du développement, j'ai eu un soucis, dans l'appli web de réservation, j'avais déjà ma méthode pour gérer les transitions, bah oui, dans le web il n'y a pas de code "natif", pour gérer ça alors, c'est à nous de le gérer.
Le souci, c'est que mon code pour gérer ça, c'était un moyen compatible avec un stack de navigation classique... En fait, même en adaptant mon code web, le fait d'avoir le bouton "retour" dans l'assistant de prise de commande, donnait une expérience utilisateur complètement nulle.
Du coup, pour ça, j'ai décidé de lancer la procédure de réservation dans un « modal natif », pour comprendre de quoi je parle, vous pouvez regarder cette vidéo sur YouTube.
Sur la vidéo, vous pouvez voir que quand je "tap" sur le bouton pour réserver, plutôt que de naviguer "en avant" et de changer toute la fenêtre, une simple fenêtre (un modal) vient par-dessus. En fait, cette partie la, est simplement géré par Turbo Navigator, il faut lui fournir un fichier .json
Fichier relativement simple, avec les « patterns » et les instructions pour ces « patterns », vous pouvez en savoir plus sur la documentation.
Une fois que ce fichier est créé, vous pouvez, soit l'héberger sur votre serveur, soit faire comme j'ai fait pour le moment l'empaqueter dans votre application, une fois que votre fichier est dans votre projet « XCode », ajoutez ceci dans votre « SceneDelegate » :
Il ne reste plus qu'à adapter votre fichier en fonction de la documentationpour que ça fonctionne comme vous le souhaiter.
Ah tient, cela rappel un des premiers articles que j'ai écrits sur ce blog...
Sauf que comme je l'ai répété plusieurs fois, on est pas dans Safari, "window.navigator.share" n'éxiste pas, en fait pendant que j'écris ces lignes je me dis que nous pourrions le « re créer » pour ne pas avoir a modifier le code existant, mais bon, ayant 100% du conrol sur le code de l'application, j'ai fait autrement, accrochez-vous, car c'est un peu plus complex.
Pour commencer, dans twig, on va faire appel à un contrôleur Stimulus :
Ouais ! on lui passe beaucoup d'info, mais bon c'est juste beaucoup de données, c'est rien.
Ensuite, on à un petit contrôler Stimulus classique :
Ici vous reconnaissez le petit "...nativeApp.postMessage", on passe les données au code natif.
Également on passe une variable « modalController », cette variable permet de dire au code natif, si notre contrôleur (celui de navigation iOS) est celui « de base » ou le « modal ».
Important pour la suite : comme vous pouvez le voir dans « Turbo Navigator », il y a deux contrôleurs, un pour la navigation « de base » et un pour la « Navigation dans les modals ».
Pour les créations d'interfaces qui suivent, iOS demande à partir de quel contrôleur il doit créer ces interfaces, de ce fait il faut que notre code passe si oui ou non nous sommes dans le modal.
Maintenant, il faut que nous récupérions tout ça dans Swift, pour faire plus simple je vais tout mettre dans la classe « TurboConfig », mais une fois que vous serez habitué à Swift vous comprendrez bien qu'il faudra séparer les classes :
La première difficulté ici, c'est qu'on doit avoir accès aux contrôleurs, c'est pourquoi tout est dans TurboConfig... à vous de nettoyer tout ça 😁, mais je pars du principe que, comme moi il y a encore une semaine, Swift, vous connaissez de nom.
Ici, je ne mets pas de vidéo, car vous avez déjà vu a quoi ressemble l'interface de partage d'iOS dans, cet article, c'est exactement la même interface ici, sauf qu'on ne peut pas l'appeler via "window.navigator.shar"
Sur iOS il n’est pas rare de voir des éléments du système embarquer dans les applications, par exemple un « Safari embarqué » c'est assez courant quand vous ouvrez un lien HTTP dans les applications.
C'est un poil plus difficile que ce que nous avons fait avant, il y a une notion en plus a prendre en compte, pour le reste, on est dans quelque chose de globalement équivalent
Dans Twig, nous allons rajouter un appel à un contrôleur Stimulus sur les liens « mailto » :
Rien de bien compliquer, vous commencer a connaitre ce genre de code 😛.
Maintenant, le contrôleur Stimulus :
Oui, il ressemble à tous les autres, en fait on ne fait que passer les données au code natif.
C'est dans le code natif que les choses se compliquent un peu :
La nouveauté ici, c'est la méthode « mailComposeController », en fait, cette popup iOS nous appel cette méthode quand l'utilisateur clique sur le bouton "fermer" du modal d'envoie d'email, du coup il faut le gérer et simplement le fermer.
Comme au-dessus j'ai tout mis dans la classe « TurboConfig », c'est plus facile pour comprendre, une fois que vous avez compris a vous d'éclater cela en plusieurs classes, et n'oubliez pas, « MFMailComposeViewController » a aussi besoin de connaitre le contrôleur de vue depuis lequel il est appelé 😁.
D'ailleurs si vous êtes observateur vous avez vu l'extension de NSObject, c'est un prérequis de cette fonction d'iOS 🙂
Si vous voulez voir ce que ça donne, vous pouvez le voir sur cette petite vidéo YouTube.
Franchement, au début je me suis dit « ça doit être simple », le « iCal » ça vient de chez Apple, non ? Donc il doit y avoir une fonction en natif qui lit ces fichiers et basta ? ... qu'est-ce que j'étais naïf 🤣 !
Déjà, dans « info.plist », il va falloir ajouter une ligne, il faut dire qu'on prévoit de travailler avec le calendrier, du coup dans votre fichier « info.plist », ajoutez : « NSCalendarsUsageDescription » avec comme valeur "Le texte présenté au visiteur quand on demande l'accès au calendrier de l'appareil"
Une fois cela fait, dans notre twig :
Là, je vous vois dites, c'est quoi « calData » ? C'est un « JSON » que je génère avec toutes les infos que normalement je mets dans le fichier iCal, pour que vous compreniez mieux la suite, voici un dump :
Bon, comme d'habitude notre contrôleur Stimulus :
Jusque là tout vous semble familier ... allez maintenant, on va dans le natif :
Ah ça tient, il y a plus de code là...
Les choses auquel il faut faire attention, et sur lesquels j'ai perdu du temps, c'est que : créer l'événement avant d'avoir le droit d'écrire dans le calendrier vous fera « crash » l'ajout au calendrier, le « state » de « l’eventStore » lié a l’événement (oui, il faut suivre), ne se met pas à jour automatiquement quand vous créer l’événement avant de demander l'autorisation...
Une petite vidéo pour voir le rendu final ?
Mon application est plutôt sombre, c'est pour rappeler l'intérieur du restaurant, le souci, c'est que pendant le chargement de la « WKWebView », on voit la vue de base (native) de l'application, et par défaut, « Turbo Native », utilise la couleur blanche comme couleur de fond.
Pour le changer, c'est à cette ligne que ça se passe.
Du coup, hormis faire un « fork » de Turbo Native pour la modifier, je n'ai pas trouvé d'autre solution.
Pour publier sur l'App Store, il vous faut un compte développeur, cela vous coutera 99€ par an.
Une fois que vous avez votre compte, rendez-vous sur « App Store Connect » et crée votre application dans l'interface, puis, vous pourrez upload votre application via « XCode ».
Le processus de vérification peut s'avérer assez stressant, dans mon cas, vu que j'ai déjà traité avec Apple par le passé, je sais ce qu'il regarde, de ce fait, ça s'est bien passé, et, je dois avouer que quand j'ai reçu l'email me disant que l'application était validée, c'était un grand soulagement.
Et voilà, cet article touche à sa fin, l'idée ici était surtout de vous mettre en avant que Turbo Native nécessite que vous vous intéressiez un minimum au fonctionnement internet d'iOS pour publier une application sur cette plateforme.
J'ai préféré appuyer sur des exemples de cas concret plutôt que de parler longtemps sur la théorie 🙂.
Passez une très bonne journée et à bientôt 😁.