Comprendre le délégué de scène iOS 13

Publié par donnywals le 28 octobre 2019Le 28 octobre 2019

Lorsque vous créez un nouveau projet dans Xcode 11, vous pouvez remarquer quelque chose que vous n’avez pas vu auparavant. Au lieu de créer uniquement un fichier AppDelegate.swift, un ViewController.swift, un storyboard et quelques autres fichiers, Xcode crée maintenant un nouveau fichier pour vous ; le fichier SceneDelegate.swift. Si vous n’avez jamais vu ce fichier auparavant, il peut être assez déroutant de comprendre ce qu’il est, et comment vous êtes censé utiliser ce nouveau délégué de scène dans votre application.

À la fin du billet de blog de cette semaine, vous saurez :

  • À quoi sert le délégué de scène.
  • Comment vous pouvez mettre en œuvre efficacement votre délégué de scène.
  • Pourquoi le délégué de scène est une partie importante d’iOS 13.

Sautons dans le vif du sujet, n’est-ce pas ?

Examen du nouveau modèle de projet Xcode

Chaque fois que vous créez un nouveau projet Xcode, vous avez la possibilité de choisir si vous voulez utiliser SwiftUI ou Storyboards. Quel que soit votre choix ici, Xcode générera un nouveau type de modèle de projet sur lequel vous pourrez vous appuyer. Nous examinerons de plus près les fichiers SceneDelegate.swift et AppDelegate.swift dans la prochaine section, ce qui est important pour l’instant est que vous compreniez que Xcode a créé ces fichiers pour vous.

En plus de ces deux fichiers de délégués, Xcode fait quelque chose d’un peu plus subtil. Regardez attentivement votre fichier Info.plist. Vous devriez voir une nouvelle clé appelée Application Scene Manifest avec un contenu similaire à l’image suivante :

Capture d'écran du manifeste de scène du fichier Info.plist

Capture d'écran du manifeste de scène du fichier Info.plist

Ce manifeste de scène spécifie un nom et une classe de délégué pour votre scène. Notez que ces propriétés appartiennent à un tableau (Application Session Role), ce qui suggère que vous pouvez avoir plusieurs configurations dans votre Info.plist. Une clé beaucoup plus importante que vous avez peut-être déjà repérée dans la capture d’écran ci-dessus est Enable Multiple Windows. Cette propriété est définie à NO par défaut. Définir cette propriété à YES permettra aux utilisateurs d’ouvrir plusieurs fenêtres de votre application sur iPadOS (ou même sur macOS). Pouvoir exécuter plusieurs fenêtres d’une application iOS côte à côte est une énorme différence par rapport à l’environnement à fenêtre unique avec lequel nous avons travaillé jusqu’à présent, et la possibilité d’avoir plusieurs fenêtres est la raison entière pour laquelle le cycle de vie de notre application est maintenant maintenu à deux endroits plutôt qu’un.

Regardons de plus près le AppDelegate et le SceneDelegate pour mieux comprendre comment ces deux délégués travaillent ensemble pour permettre la prise en charge de plusieurs fenêtres.

Comprendre les rôles d’AppDelegate et de SceneDelegate

Si vous avez construit des applications avant iOS 13, vous connaissez probablement votre AppDelegate comme le seul endroit qui fait à peu près tout ce qui concerne le lancement de votre application, l’avant-plan, l’arrière-plan et même plus. Dans iOS 13, Apple a déplacé certaines des responsabilités du AppDelegate vers le SceneDelegate. Jetons un bref coup d’œil à chacun de ces deux fichiers.

Responsabilités de l’AppDelegate

Le AppDelegate est toujours le principal point d’entrée d’une application dans iOS 13. Apple appelle les méthodes AppDelegate pour plusieurs événements du cycle de vie au niveau de l’application. Dans le modèle par défaut d’Apple, vous trouverez trois méthodes qu’Apple considère comme importantes pour que vous les utilisiez :

  • func application(_:didFinishLaunchingWithOptions:) -> Bool
  • func application(_:configurationForConnecting:options:) -> UISceneConfiguration
  • func application(_:didDiscardSceneSessions:)

Ces méthodes comportent des commentaires qui décrivent réellement ce qu’elles font de manière suffisamment détaillée pour que vous compreniez ce qu’elles font. Mais passons-les quand même rapidement en revue.

Lorsque votre application vient d’être lancée, func application(_:didFinishLaunchingWithOptions:) -> Bool est appelée. Cette méthode est utilisée pour effectuer la configuration de l’application. Dans iOS 12 ou plus tôt, vous auriez pu utiliser cette méthode pour créer et configurer un objet UIWindow et affecter une instance UIViewController à la fenêtre pour la faire apparaître.

Si votre application utilise des scènes, votre AppDelegate n’est plus chargé de le faire. Puisque votre application peut maintenant avoir plusieurs fenêtres, ou UISceneSessions actives, cela n’a pas beaucoup de sens de gérer un seul objet fenêtre dans le AppDelegate.

La func application(_:configurationForConnecting:options:) -> UISceneConfiguration est appelée chaque fois que votre application est censée fournir une nouvelle scène, ou une fenêtre à afficher pour iOS. Notez que cette méthode n’est pas appelée lorsque votre application se lance initialement, elle est uniquement appelée pour obtenir et créer de nouvelles scènes. Nous examinerons plus en profondeur la création et la gestion de plusieurs scènes dans un prochain billet de blog.

La dernière méthode du modèle AppDelegate est func application(_:didDiscardSceneSessions:). Cette méthode est appelée chaque fois qu’un utilisateur jette une scène, par exemple en la faisant glisser dans la fenêtre multitâche ou si vous le faites de manière programmatique. Si votre app n’est pas en cours d’exécution lorsque l’utilisateur fait cela, cette méthode sera appelée pour chaque scène jetée peu de temps après l’appel de func application(_:didFinishLaunchingWithOptions:) -> Bool.

En plus de ces méthodes par défaut, votre AppDelegate peut encore être utilisé pour ouvrir des URL, attraper des avertissements de mémoire, détecter quand votre app se terminera, si l’horloge de l’appareil a changé de manière significative, détecter quand un utilisateur s’est inscrit pour des notifications à distance et plus encore.

Conseil:
Il est important de noter que si vous utilisez actuellement AppDelegate pour gérer l’apparence de la barre d’état de votre app, vous pourriez avoir à faire quelques changements dans iOS 13. Plusieurs méthodes liées à la barre d’état ont été dépréciées dans iOS 13.

Maintenant que nous avons une meilleure image de ce que sont les nouvelles responsabilités de votre AppDelegate, jetons un coup d’œil au nouveau SceneDelegate.

Responsabilités du SceneDelegate

Lorsque vous considérez le AppDelegate comme l’objet responsable du cycle de vie de votre application, le SceneDelegate est responsable de ce qui est affiché à l’écran ; les scènes ou les fenêtres. Avant de continuer, établissons un vocabulaire relatif aux scènes, car tous les termes ne signifient pas ce que vous pensez qu’ils signifient.

Lorsque vous traitez des scènes, ce qui ressemble à une fenêtre pour votre utilisateur est en fait appelé une UIScene qui est gérée par une UISceneSession. Ainsi, lorsque nous faisons référence à des fenêtres, nous nous référons en réalité à des objets UISceneSession. Je vais essayer de m’en tenir à cette terminologie autant que possible tout au long de ce billet de blog.

Maintenant que nous sommes sur la même page, regardons le fichier SceneDelegate.swift que Xcode a créé lorsqu’il a créé notre projet.

Il y a plusieurs méthodes dans le fichier SceneDelegate.swift par défaut :

  • scene(_:willConnectTo:options:)
  • sceneDidDisconnect(_:)
  • sceneDidBecomeActive(_:)
  • sceneWillResignActive(_:)
  • sceneWillEnterForeground(_:)
  • sceneDidEnterBackground(_:)

Ces méthodes devraient vous sembler très familières si vous connaissez les AppDelegate qui existaient avant iOS 13. Regardons d’abord scene(_:willConnectTo:options:), cette méthode semble probablement la moins familière pour vous et c’est la première méthode appelée dans le cycle de vie d’un UISceneSession.

L’implémentation par défaut de scene(_:willConnectTo:options:) crée votre vue de contenu initiale (ContentView si vous utilisez SwiftUI), crée une nouvelle UIWindow, définit la rootViewController de la fenêtre et fait de cette fenêtre la fenêtre clé. Vous pourriez penser que cette fenêtre est la fenêtre que votre utilisateur voit. Ce n’est malheureusement pas le cas. Les fenêtres existent depuis avant iOS 13 et elles représentent le viewport dans lequel votre application fonctionne. Ainsi, le UISceneSession contrôle la fenêtre visible que l’utilisateur voit, le UIWindow que vous créez est la vue conteneur de votre application.

En plus de configurer les vues initiales, vous pouvez utiliser scene(_:willConnectTo:options:) pour restaurer l’interface utilisateur de votre scène au cas où celle-ci se serait déconnectée dans le passé. Par exemple, parce qu’elle a été envoyée en arrière-plan. Vous pouvez également lire l’objet connectionOptions pour savoir si votre scène a été créée en raison d’une demande de HandOff ou peut-être pour ouvrir une URL. Je vous montrerai comment faire cela plus tard dans ce billet de blog.

Une fois que votre scène s’est connectée, la prochaine méthode dans le cycle de vie de votre scène est sceneWillEnterForeground(_:). Cette méthode est appelée lorsque votre scène va entrer en scène. Cela pourrait être lorsque votre application passe de l’arrière-plan au premier plan, ou si elle devient active pour la première fois. Ensuite, la méthode sceneDidBecomeActive(_:) est appelée. C’est le moment où votre scène est mise en place, visible et prête à être utilisée.

Lorsque votre application passe à l’arrière-plan, sceneWillResignActive(_:) et sceneDidEnterBackground(_:) sont appelées. Je ne m’étendrai pas sur ces méthodes pour l’instant car leur but varie pour chaque application, et les commentaires du modèle Xcode font un assez bon travail pour expliquer quand ces méthodes sont appelées. En fait, je suis sûr que vous pouvez déterminer vous-même le moment où ces méthodes sont appelées.

Une méthode plus intéressante est sceneDidDisconnect(_:). Chaque fois que votre scène est envoyée en arrière-plan, iOS pourrait décider de se déconnecter et de vider votre scène pour libérer des ressources. Cela ne signifie pas que votre scène a été tuée ou ne fonctionne plus, cela signifie simplement que la scène passée à cette méthode n’est plus active et sera déconnectée de sa session.

Notez que la session elle-même n’est pas nécessairement abandonnée aussi, iOS pourrait décider de reconnecter une scène à une session de scène à tout moment, par exemple lorsqu’un utilisateur ramène une scène particulière au premier plan.

La chose la plus importante à faire dans sceneDidDisconnect(_:) est de se débarrasser de toutes les ressources que vous n’avez pas besoin de garder autour. Cela pourrait être des données qui sont facilement chargées à partir du disque ou du réseau ou d’autres données que vous pouvez recréer facilement. Il est également important de s’assurer que vous conservez toutes les données qui ne peuvent pas être facilement recréées, comme par exemple toute entrée que l’utilisateur a fournie dans une scène et dont il s’attendrait à ce qu’elle soit toujours présente lorsqu’il retourne à une scène.

Envisagez une application de traitement de texte qui prend en charge plusieurs scènes. Si un utilisateur travaille dans une scène, puis l’arrière-plan pour faire des recherches sur Safari et changer sa musique dans Spotify, il s’attendrait absolument à ce que tout son travail existe toujours dans l’app de traitement de texte, même si iOS a pu déconnecter la scène de l’app de traitement de texte pendant un moment. Pour y parvenir, l’app doit conserver les données nécessaires, et elle doit encoder l’état actuel de l’app dans un objet NSUserActivity qui peut être lu plus tard dans scene(_:willConnectTo:options:) lorsque la scène est reconnectée.

Puisque ce flux de travail de connexion, de déconnexion et de reconnexion des scènes va séparer les bonnes apps des excellentes, regardons comment vous pouvez mettre en œuvre la restauration de l’état dans votre app.

Préparation de scènes supplémentaires

Il existe plusieurs raisons pour lesquelles vous devez effectuer une configuration supplémentaire lorsqu’une scène est configurée. Vous pourriez avoir à ouvrir une URL, à traiter une demande de Handoff ou à restaurer l’état. Dans cette section, je me concentrerai surtout sur la restauration d’état puisque c’est probablement le scénario le plus complexe que vous pourriez avoir à gérer.

La restauration d’état commence lorsque votre scène est déconnectée et que sceneDidDisconnect(_:) est appelé. À ce stade, il est important que votre application ait déjà un état mis en place qui peut être restauré plus tard. La meilleure façon de le faire est d’utiliser NSUserActivity dans votre application. Si vous utilisez NSUserActivity pour prendre en charge Handoff, les raccourcis Siri, l’indexation Spotlight et plus encore, vous n’avez pas beaucoup de travail supplémentaire à faire. Si vous n’utilisez pas encore NSUserActivity, ne vous inquiétez pas. Une activité utilisateur simple pourrait ressembler un peu à ce qui suit :

let activity = NSUserActivity(activityType: "com.donnywals.DocumentEdit")activity.userInfo = 

Notez que cette activité utilisateur n’est pas structurée comme Apple le recommande, c’est un exemple très dépouillé destiné à illustrer la restauration d’état. Pour un guide complet sur NSUserActivity, je vous recommande de jeter un coup d’œil à la documentation d’Apple sur ce sujet.

Lorsque le moment est venu pour vous de fournir une activité utilisateur qui peut être restaurée à un moment ultérieur, le système appelle la méthode stateRestorationActivity(for:) sur votre SceneDelegate. Notez que cette méthode ne fait pas partie du modèle par défaut

func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { return scene.userActivity}

Ce faisant, vous associez l’activité utilisateur actuellement active pour une scène à la session de la scène. Rappelez-vous que chaque fois qu’une scène est déconnectée, le UISceneSession qui possède le UIScene n’est pas écarté pour permettre à la session de se reconnecter à une scène. Lorsque cela se produit, scene(_:willConnectTo:options:) est appelé à nouveau. Dans cette méthode, vous avez accès au UISceneSession qui possède le UIScene afin de pouvoir lire le stateRestorationActivity de la session et restaurer l’état de l’application si nécessaire :

if let activity = session.stateRestorationActivity, activity.activityType == "com.donnywals.DocumentEdit", let documentId = activity.userInfo as? String { // find document by ID // create document viewcontroller and present it}

Bien sûr, les détails fins de ce code varieront en fonction de votre application, mais l’idée générale devrait être claire.

Si votre UISceneSession est censé gérer une URL, vous pouvez inspecter le urlContexts de l’objet connectionOptions pour trouver les URL que votre scène doit ouvrir et les informations sur la façon dont votre application doit le faire:

for urlContext in connectionOptions.urlContexts { let url = urlContext.url let options = urlContext.options // handle url and options as needed}

L’objet options contiendra des informations sur le fait que votre scène doit ouvrir l’URL en place, quelle application a demandé l’ouverture de cette URL et d’autres métadonnées sur la demande.

Les bases de la restauration d’état dans iOS 13 avec l’objet SceneDelegate sont étonnamment simples, en particulier parce qu’il est construit sur NSUserActivity, ce qui signifie que beaucoup d’applications n’auront pas à faire trop de travail pour commencer à supporter la restauration d’état pour leurs scènes.

N’oubliez pas que si vous voulez avoir le support de plusieurs scènes pour votre application sur iPadOS, la restauration de scène est particulièrement importante car iOS pourrait déconnecter et reconnecter vos scènes lorsqu’elles passent du premier plan à l’arrière-plan et inversement. Surtout si votre application permet à un utilisateur de créer ou de manipuler des objets dans une scène, un utilisateur ne s’attendrait pas à ce que son travail disparaisse s’il déplace une scène à l’arrière-plan pendant un moment.

En résumé

Dans ce billet de blog, vous avez appris beaucoup de choses. Vous avez appris quels rôles les AppDelegate et SceneDelegate remplissent dans iOS 13 et à quoi ressemblent leurs cycles de vie. Vous savez maintenant que le AppDelegate est chargé de réagir aux événements de niveau applicatif, comme le lancement d’une application par exemple. Le SceneDelegate est responsable des événements liés au cycle de vie des scènes. Par exemple, la création de la scène, la destruction et la restauration de l’état d’un UISceneSession. En d’autres termes, la principale raison pour laquelle Apple a ajouté UISceneDelegate à iOS 13 était de créer un bon point d’entrée pour les applications multifenêtres.

Après avoir appris les bases de UISceneDelegate, vous avez vu un exemple très simple de ce à quoi ressemble la restauration d’état dans iOS 13 avec UISceneSession et UIScene. Bien sûr, il y a beaucoup plus à apprendre sur la façon dont votre application se comporte lorsqu’un utilisateur spawns plusieurs UISceneSessions pour votre application, et comment ces scènes pourraient avoir à rester synchronisées ou à partager des données.

Si vous voulez en savoir plus sur la prise en charge de plusieurs fenêtres pour votre application iPad (ou votre application macOS), assurez-vous de consulter mon post Ajout de la prise en charge de plusieurs fenêtres à votre application iPadOS. Merci de votre lecture, et n’hésitez pas à m’atteindre sur Twitter si vous avez des questions ou des commentaires pour moi.

Rester à jour avec mon bulletin hebdomadaire

Combine pratique

Apprenez tout ce que vous devez savoir sur Combine et comment vous pouvez l’utiliser dans vos projets avec mon nouveau livre Combine pratique. Vous disposerez de treize chapitres, d’un Playground et d’une poignée d’exemples de projets pour vous aider à être opérationnel avec Combine le plus rapidement possible.

Le livre est disponible en téléchargement numérique pour seulement 29,99 $ !

Obtenez Combine pratique

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.