1er Développement sur Apple Watch, iOS

Feedback sur la première fois avec l'Apple Watch

who's next Chez AppSonic, nous avions développé une application de tirage au sort (Whosnext) sur iPhone assez simple, et qui répondait d’abord à un besoin qu’on avait : pouvoir choisir quelqu’un de façon aléatoire mais équitable ! Pour faire ça, l’application permet de faire un tirage au sort sur des listes de n’importe quoi. Mais en plus de tirer au sort, elle “coche” l’élément tiré au sort de façon à ce que ce dernier ne puisse plus l’être jusqu’à ce que tous les autres éléments de la liste aient à leur tour été tirés au sort.

A titre personnel, on se sert souvent de l’application. Si on liste les étapes que l’utilisateur suit pour faire un tirage, ça donne :

  • sortir le téléphone de sa poche,
  • le dévérouiller,
  • trouver l’application,
  • lancer l’application,
  • choisir une question,
  • lancer le tirage

A l’usage, toutes les étapes pour faire un tirage au sort sont donc assez rapides à réaliser mais on s’est dit que l’application serait encore plus géniale si tout ceci était presque immédiat.

C’est là que l’Apple Watch peut révéler tout son potentiel : obtenir des informations de façon plus immédiate qu’avec un Smartphone.

Nous voilà donc partis pour faire les développements nécessaires pour avoir une version sur l’Apple Watch !

Nous nous sommes posé des questions tout au long de cette aventure et on s’est dit que ça pouvait en intéresser d’autres: vous les trouverez donc ici ainsi que les réponses qu’on a trouvées.

Cet article ne se veut pas un guide exhaustif de comment faire une application, mais on espère que ça vous fera gagner du temps si vous vous lancez dans le développement d’une application Watch.

Quelle fonctionnalité mettre sur la Watch ?

Et oui, c’est la première question à se poser : avec un écran tout petit, il faut adapter et simplifier encore plus les applications.

Chez AppSonic, nous utilisons un logiciel de gestion de projets (Jira avec le module Agile) pour organiser tous nos travaux et nous avons donc une certaine pratique pour découper les fonctionnalités d’une application en tâches atomiques. C’est ce que nous avons fait pour Whosnext. Pour vous donner une idée, ça donne :

  1. Afficher la liste des questions.
  2. Afficher le dernier résultat d’une question.
  3. Faire un tirage au sort.
  4. Réinitialiser les coches.
  5. Switcher avec/sans coche.
  6. Afficher les éléments d’une question.
  7. Partager le dernier résultat par mail.
  8. Partager une question par mail.
  9. Envoyer un tirage complet par mail.
  10. Ajouter une question.
  11. Copier une question existante.
  12. Supprimer une question.
  13. Ajouter un élément à une question.
  14. Modifier un élément.
  15. Supprimer un élément.
  16. Ajouter un élément en utilisant le carnet d’adresse.

Il faut préciser que l’Apple Watch n’était pas disponible au moment où nous avons fait ces développments, aussi nous ne disposions que du simulateur via XCode et des vidéos promotionnelles d’Apple pour se faire une idée de comment une application marchait sur la Watch.

Avec autant d’inconnues et un objectif daté, nous voulions que l’application soit compatible avec la Watch le jour de sa sortie. Nous avons donc limité notre première version aux fonctionnalités indispensables. Pour faire ça, sur chaque fonctionnalité, nous nous sommes demandé si sans celle-ci l’utilisateur pouvait toujours faire un tirage au sort car c’est le but premier de cette application. Du coup, nous avons réduit les fonctionnalités à :

  1. Afficher la liste des questions.
  2. Afficher le dernier résultat d’une question.
  3. Faire un tirage au sort.

C’est toujours un exercice difficile car on a envie d’offrir plus aux utilisateurs, mais il vaut mieux procéder par étapes et commencer par peu de fonctionnalités qui marchent parfaitement plutôt que beaucoup de fonctionnalités qui rendent l’interface plus complexe, plus compliquée à utiliser et qui augmentent les risques d’avoir des bogues.

Comment fonctionne une application sur la Watch ?

Une fois le périmètre défini, nous sommes rentrés dans le vif du sujet : les développements.

Comme d’habitude chez Apple, les documentations officielles sont très bien faites et je ne peux que vous recommander vivement leur lecture.

Pour ceux qui ne le sauraient pas, l’Apple Watch ne peut pas faire fonctionner d’application sans iPhone. En fait, on peut voir la Watch comme un écran déporté, ce qui veut dire que toute la logique, les calculs, le stockage des données et les interactions réseaux sont réalisées sur l’iPhone. La Watch affiche les interfaces et capte les interactions avec l’utilisateur.

Apple WatchKit App Architecture

Pour faire une application sur la Watch, il faut donc 3 éléments minimum :

  • Une application iPhone “classique”
  • Une extension Watch (nommé WatchKit Extension)
  • Une interface (un Storyboard) et des assets (nommé WatchKit App)

L’application encapsule l’extension qui encapsule l’interface. Seule la troisième partie est installée sur la Watch.

Pour démarrer dans XCode, rien de plus simple, on crée une nouvelle target de type WatchKit et XCode configure tout pour nous et crée aussi bien l’extension que l’application Watch avec une première interface.

Quel est le cycle de vie de l’application Watch ?

Quand l’application sur la Watch démarre, l’extension est exécutée en parallèle sur l’iPhone et doit piloter l’interface affichée sur la Watch. La Watch communique à l’extension toutes les interactions avec l’utilisateur.

Contrairement à une application iPhone, une application Watch ne peut pas fonctionner en background et le processus de l’extension est arrêté dès que l’utilisateur quitte l’application sur la Watch.

Un écran du Storyboard correspond à un WKInterfaceController (l’équivalent Watch d’un UIViewController iPhone).

Au 1er démarrage, la Watch utilise l’écran indiqué comme initial dans le Storyboard, ensuite si l’utilisateur sort de l’application et la rappelle plus tard, c’est le dernier écran affiché qui est utilisé. Il faut donc faire attention dans les callbacks dédiées que cet écran soit toujours d’actualité, sans quoi il faut le rafraichir ou afficher un autre écran le cas échéant.

Il y a 3 façons de démarrer une application Watch :

  • Via l’icone des applications, dans ce cas c’est l’écran “initial” ou le dernier écran affiché qui est utilisé
  • Via une vue très simple appelée “Glance” qui doit se limiter à un affichage en lecture seule et qui correspond à un aperçu rapide de l’application. Cette vue est accessible via un slide vers le bas depuis la vue heure. Ce slide permet d’afficher toutes les “Glances” des applications sous forme de vue paginée (slide gauche / droite pour passer d’une “Glance” à l’autre).
  • Via des notifications pour lesquelles on peut personnaliser l’affichage ou utiliser la vue par défaut.

Nous n’avons pas de notification pour le moment dans Whosnext et nous n’avions pas le temps de faire de “Glance” donc nous avons uniquement utilisé la première option.

Il y a peu de callbacks :

  • La première fois qu’un écran est affiché (ou si l’application a été supprimée de la mémoire), les méthodes init puis awakeWithContext: sont appelées.
  • Ensuite avant chaque affichage, la méthode willActivate est appelée.
  • Enfin quand l’utilisateur quitte l’application (ou vérouille l’écran) la méthode didDeactivate est appelée.

Voici les schémas correspondants : Cycle de vie d'une application Cycle de vie d'un WKInterfaceController

Comment partager du code entre la Watch et l’iPhone ?

Bon maintenant qu’on sait faire une application sur la Watch, on a tout de suite envie de brancher nos services métiers iPhone pour pouvoir afficher nos données !

La façon mise en avant par Apple (et qu’on a utilisée) est de passer par les nouveaux Cocoa Touch Framework qui se veulent un système enfin un peu pratique pour partager du code sur iOs. Ils ne sont utilisables que sur les devices avec des iOs > 8.0. Auparavant, sans passer par des gestionnaires de dépendances comme CocoaPod par exemple, on copiait le code dans les 20 projets où on en avait besoin, ou on passait par des librairies “.a” mais dans lesquelles on ne pouvait pas mettre de ressources.

Ils apportent surtout le fait que le code présent dans un framework est chargé dynamiquement à l’exécution et non copié dans l’exécutable (si on passe par des “.a”) au moement de la compilation. Ceci permet de limiter la taille des applications notamment si une application embarque plusieurs extensions.

Autre point qui peut être sympa, c’est que XCode peut lui aussi les charger dynamiquement et du coup l’Interface Builder est capable de dessiner les composants personnalisés qui sont dans les frameworks.

Du coup on crée une nouvelle target de type Cocoa Touch Framework et on déplace le code qui doit être en commun dans le Framework.

Comment partager des données entre l’iPhone et la Watch ?

Un framework c’est bien pour partager du code, mais on n’a toujours pas accès aux données de l’application : l’extension et l’application iPhone sont exécutées dans 2 sandbox différentes.

Pour partager des fichiers ou des préférences (NSUserDefaults) entre 2 applications ou une application et son extension, il faut utiliser le même app group. Ca se configure dans l’onglet Capabilities d’XCode.

Notre application Whosnext stockait tout dans le NSUserDefaults par défaut. Pour éviter que les utilisateurs ne perdent leurs données, nous avons dû les déplacer au démarrage dans le NSUserDefault de l’app group.

C’est ce genre de petite subtilité technique que vous ne pouvez pas estimer au démarrage du projet qui vous fait vite perdre un petit peu de temps sur votre planning initial !

Comment communiquer de la watch vers l’iPhone ?

Comme l’extension a une durée de vie très sommaire, il faut déléguer toutes les interactions réseaux et même la pluspart des actions métiers à l’application iPhone. Dans notre cas, c’est l’application iPhone qui se charge de faire le tirage au sort pour 2 raisons :

  • Si l’application Watch et l’application iPhone sont lancées simultanément, on ne veut pas qu’il y ait de problème de concurrence si 2 tirages au sort sont lancés au même moment sur la même question.
  • Si un utilisateur fait un tirage au sort sur la Watch, on veut que l’application iPhone se mette à jour en conséquence (elle doit donc être avertie de rafraîchir son interface).

Personnellement, je trouve que c’est très facile et plutôt bien fait dans WatchKit car pour envoyer et recevoir une réponse de l’application iPhone depuis l’extension Watch, il suffit d’appeler sur WKInterfaceController la méthode openParentApplication:reply: à laquelle on passe 2 paramètres :

  • Un NSDictionary qui est transmis à l’application iPhone
  • Un block qui sera éxécuté lorsque l’application iPhone aura répondu. Ce block reçoit 2 paramètres : un NSDictionary qui correspond à la réponse de l’application et un NSError si une erreur s’est produite.

L’application iPhone est démarrée en background, reçoit le message application:handleWatchKitExtensionRequest:reply: via son Application Delegate et fait ce qu’elle a à faire ! Lorsqu’elle veut répondre, elle utilise le block passé en 3ème paramètre. Attention, il ne faut pas oublier dans cette méthode de demander du temps en background via beginBackgroundTaskWithExpirationHandler: par exemple.

Dans le forum dédié à la Watch, Apple conseille d’appeler endBackgroundTask: au moins 2 secondes après avoir répondu (via le block passé en 3ème paramètre) pour laisser le temps à l’application de communiquer avec la Watch. C’est le seul point que je trouve un peu complexe mais je ne vois pas comment ils auraient pu faire autrement. C’est d’autant plus complexe que je n’ai trouvé l’information que sur le forum.

Bien sûr la communication peut prendre un petit peu de temps et il faut prévoir dans l’interface de la Watch le fait que lorsqu’on utilise cette méthode, il y a un certain délai de réponse (c’est encore plus vrai lorsque l’application iPhone n’est pas encore lancée).

Comment communiquer de l’iPhone vers la Watch ?

On voulait pouvoir mettre à jour l’interface de la Watch lorsque l’iPhone fait un tirage au sort.

Pour ça, il fallait que l’iPhone prévienne la Watch qu’un tirage au sort avait eu lieu.

Or pour le moment, il n’y a rien de réellement prévu pour faire ça, à part :

  • Utiliser un NSDocument (c’est ce que fait Apple dans son exemple “Lister”). Si on n’utilise pas de NSDocument c’est pas top du coup !
  • Sur le net plusieurs personnes utilisent Darwin Notification Center. Vous pouvez en lire plus à ce sujet ici.
  • Core Data doit marcher, je suppose …

Vu notre cas d’utilisation, nous n’avons finalement rien fait car :

  • Les interactions sur la Watch étant courtes, il y a peu de chance qu’un utilisateur s’amuse à voir si les 2 applications sont synchornisées en temps réel. En tout cas, il n’en n’aurait pas l’utilité.
  • On avait déjà fait les développements pour rafraîchir correctement l’interface via la méthode willActivate pour gérer correctement le redémarrage de l’application.
  • On n’utilisait ni de NSDocument, ni de Code Data.

Nous avons donc estimé que pour une première version, le coût de développement était bien trop élevé par rapport au gain pour l’utilisateur.

Comment faire une animation ?

La manipulation via l’extension des éléments de l’interface de la Watch est très limitée.

On ne peut que :

  • Modifier la valeur des données.
  • Changer l’apparence de certains éléments (qui sont prévus pour dans le SDK).
  • Changer la taille d’un élément.
  • Changer sa transparence.
  • Cacher ou afficher un élément.

On ne peut pas ajouter ou supprimer d’élément dynamiquement. On ne peut pas non plus changer l’ordre des éléments.

La seule façon de faire une animation c’est d’utiliser une séquence d’images.

Les images sont soit embarquées dans les ressources de l’application Watch, soit envoyées dynamiquement par l’extension dans la Watch. La Watch possède un mécanisme de cache d’images.

Nous avons utilisé [WKInterfaceImage setImageNamed:] qui permet via un nom de base comme @"watch-anim" de construire la séquence d’images nommées : “watch-animXXX.png” ou XXX correspond à un numéro (1, 2, …).

Comment gérer 2 applications (une gratuite et une payante) ?

Whosnext exite dans 2 versions : une gratuite et une payante (Whosnext Pro). Les 2 applications sont identiques, mais la version Pro ne contient pas de pub.

Nous voulions que les 2 versions de l’application aient une version Watch.

Pour ça, il suffit de créer 2 targets de type Watch avec 2 Bundle Identifier différents.

Pour ne pas tout dupliquer, vous pouvez :

  • supprimer tout ce qui existe dans la 2ème extension et la 2ème application Watch à l’exception du fichier info.plist,
  • packager tous les fichiers de la 1ère extension pour les 2 extensions,
  • packager tous les fichiers de la 1ére application Watch pour les 2 applications Watch.

Pour packager des fichiers avec plusieurs cibles, j’utilise le volet “Target Membership” de l’onglet “File inspector” (⌥⌘1)

J’ai eu quelques problèmes au moment de lancer les applications Watch dans le simmulateur. Il ne faut pas hésiter à faire des Clean sur les applications iPhone quand ça arrive.

Comment gérer un nom d’application internationnalisé sur la Watch ?

C’est exactement pareil que pour l’application iPhone, il suffit de mettre un fichier InfoPlist.strings dans chaque langue (comme le fichier Localizable.strings), packagé avec l’application Watch contenant :

"CFBundleDisplayName" = "A qui l'tour";
"CFBundleName" = "A qui l'tour";

J’ai aussi mis ce fichier dans l’extension …

Il y a un comportement étrange dans l’application Apple Watch de l’iPhone car dans cette dernière les applications apparaîssent avec le nom du binaire et non la valeur de la clé CFBundleDisplayName. Visiblement, ceci pourrait changer dans le futur …

Cf : https://developer.apple.com/library/ios/qa/qa1892/_index.html

Pour aller plus loin

Tous les liens les plus utiles sont ici : https://developer.apple.com/watchkit/

Dedans vous trouverez notamment :



comments powered by Disqus