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 :
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.
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 :
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 à :
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.
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.
Pour faire une application sur la Watch, il faut donc 3 éléments minimum :
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.
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 :
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 :
init
puis awakeWithContext:
sont appelées.willActivate
est appelée.didDeactivate
est appelée.Voici les schémas correspondants :
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.
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 !
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 :
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 :
NSDictionary
qui est transmis à l’application iPhoneNSDictionary
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).
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 :
NSDocument
(c’est ce que fait Apple dans son exemple “Lister”). Si on n’utilise pas de NSDocument
c’est pas top du coup !Vu notre cas d’utilisation, nous n’avons finalement rien fait car :
willActivate
pour gérer correctement le redémarrage de l’application.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.
La manipulation via l’extension des éléments de l’interface de la Watch est très limitée.
On ne peut que :
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, …).
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 :
info.plist
,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.
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
Tous les liens les plus utiles sont ici : https://developer.apple.com/watchkit/
Dedans vous trouverez notamment :