Entender el delegado de escena de iOS 13

Publicado por donnywals el 28 de octubre de 2019October 28, 2019

Cuando creas un nuevo proyecto en Xcode 11, puede que notes algo que no habías visto antes. En lugar de crear solo un archivo AppDelegate.swift, un ViewController.swift, un storyboard y algunos otros archivos, Xcode ahora crea un nuevo archivo para ti; el archivo SceneDelegate.swift. Si usted nunca ha visto este archivo antes, puede ser bastante confuso para entender lo que es, y cómo se supone que debe utilizar este nuevo delegado de escena en su aplicación.

Al final de la entrada del blog de esta semana usted sabrá:

  • Lo que el delegado de escena se utiliza para.
  • Cómo se puede implementar efectivamente su delegado de escena.
  • Por qué el delegado de escena es una parte importante de iOS 13.

Entremos de lleno, ¿de acuerdo?

Examinando la nueva plantilla de proyecto de Xcode

Cada vez que creas un nuevo proyecto de Xcode, tienes la opción de elegir si quieres usar SwiftUI o Storyboards. Independientemente de su elección aquí, Xcode generará un nuevo tipo de plantilla de proyecto para que usted pueda construir. Vamos a echar un vistazo más de cerca a los archivos SceneDelegate.swift y AppDelegate.swift en la siguiente sección, lo que es importante por ahora es que usted entiende que Xcode ha creado estos archivos para usted.

Además de estos dos archivos de delegado, Xcode hace algo un poco más sutil. Echa un vistazo a tu archivo Info.plist. Deberías ver una nueva clave llamada Application Scene Manifest con un contenido similar al de la siguiente imagen:

Captura del manifiesto de escena del archivo Info.plist

Captura del manifiesto de escena del archivo Info.plist

Este manifiesto de escena especifica un nombre y una clase de delegado para tu escena. Observa que estas propiedades pertenecen a un array (Application Session Role), lo que sugiere que puedes tener múltiples configuraciones en tu Info.plist. Una clave mucho más importante que ya puede haber visto en la captura de pantalla anterior es Enable Multiple Windows. Esta propiedad se establece en NO por defecto. Configurar esta propiedad a YES permitirá a los usuarios abrir múltiples ventanas de tu aplicación en iPadOS (o incluso en macOS). Ser capaz de ejecutar múltiples ventanas de una aplicación iOS una al lado de la otra es una gran diferencia con el entorno de una sola ventana con el que hemos trabajado hasta ahora, y la capacidad de tener múltiples ventanas es toda la razón por la que el ciclo de vida de nuestra aplicación se mantiene ahora en dos lugares en lugar de uno.

Vamos a echar un vistazo más de cerca al AppDelegate y al SceneDelegate para entender mejor cómo estos dos delegados trabajan juntos para permitir el soporte de múltiples ventanas.

Entendiendo los roles de AppDelegate y SceneDelegate

Si has construido aplicaciones antes de iOS 13, probablemente conozcas tu AppDelegate como el único lugar que hace prácticamente todo lo relacionado con el lanzamiento de tu aplicación, el primer plano, el segundo plano y algo más. En iOS 13, Apple ha trasladado algunas de las responsabilidades del AppDelegate al SceneDelegate. Echemos un breve vistazo a cada uno de estos dos archivos.

Responsabilidades deAppDelegate

El AppDelegate sigue siendo el principal punto de entrada de una aplicación en iOS 13. Apple llama a los métodos AppDelegate para varios eventos del ciclo de vida a nivel de aplicación. En la plantilla por defecto de Apple encontrarás tres métodos que Apple considera importantes para que los utilices:

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

Estos métodos tienen algunos comentarios que realmente describen lo que hacen con suficiente detalle para entender lo que hacen. Pero vamos a ir sobre ellos rápidamente de todos modos.

Cuando su aplicación se acaba de lanzar, func application(_:didFinishLaunchingWithOptions:) -> Bool se llama. Este método se utiliza para realizar la configuración de la aplicación. En iOS 12 o anterior, es posible que haya utilizado este método para crear y configurar un objeto UIWindow y asignar una instancia UIViewController a la ventana para que aparezca.

Si su aplicación está utilizando escenas, su AppDelegate ya no es responsable de hacer esto. Dado que su aplicación ahora puede tener múltiples ventanas, o UISceneSessions activo, no tiene mucho sentido para gestionar un objeto de una sola ventana en el AppDelegate.

El func application(_:configurationForConnecting:options:) -> UISceneConfiguration se llama cada vez que su aplicación se espera que el suministro de una nueva escena, o ventana para iOS para mostrar. Tenga en cuenta que este método no se llama cuando su aplicación se lanza inicialmente, sólo se llama para obtener y crear nuevas escenas. Vamos a echar un vistazo más profundo a la creación y gestión de múltiples escenas en una entrada del blog posterior.

El último método en la plantilla AppDelegate es func application(_:didDiscardSceneSessions:). Este método se llama cada vez que un usuario descarta una escena, por ejemplo, deslizándola en la ventana multitarea o si lo hace mediante programación. Si su aplicación no se está ejecutando cuando el usuario hace esto, este método será llamado para cada escena descartada poco después de llamar a func application(_:didFinishLaunchingWithOptions:) -> Bool.

Además de estos métodos por defecto, su AppDelegate todavía se puede utilizar para abrir URLs, capturar advertencias de memoria, detectar cuando su aplicación va a terminar, si el reloj del dispositivo cambió significativamente, detectar cuando un usuario se ha registrado para las notificaciones remotas y más.

Consejo:
Es importante tener en cuenta que si actualmente utilizas AppDelegate para gestionar la apariencia de la barra de estado de tu app, puede que tengas que hacer algunos cambios en iOS 13. Varios métodos relacionados con la barra de estado han quedado obsoletos en iOS 13.

Ahora que tenemos una mejor idea de cuáles son las nuevas responsabilidades de tu AppDelegate, echemos un vistazo al nuevo SceneDelegate.

Responsabilidades del Delegado de Escena

Cuando consideras que el AppDelegate es el objeto responsable del ciclo de vida de tu aplicación, el SceneDelegate es responsable de lo que se muestra en la pantalla; las escenas o ventanas. Antes de continuar, establezcamos algo de vocabulario relacionado con las escenas porque no todos los términos significan lo que crees que significan.

Cuando tratas con escenas, lo que parece una ventana para tu usuario se llama en realidad un UIScene que es gestionado por un UISceneSession. Así que cuando nos referimos a las ventanas, en realidad nos estamos refiriendo a los objetos UISceneSession. Intentaré ceñirme a esta terminología en la medida de lo posible a lo largo de esta entrada del blog.

Ahora que estamos en la misma página, veamos el archivo SceneDelegate.swift que Xcode creó cuando creó nuestro proyecto.

Hay varios métodos en el archivo SceneDelegate.swift por defecto:

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

Estos métodos deberían resultarte muy familiares si estás familiarizado con el AppDelegate que existía antes de iOS 13. Vamos a echar un vistazo a scene(_:willConnectTo:options:) primero, este método probablemente parece menos familiar para usted y es el primer método llamado en el ciclo de vida de un UISceneSession.

La implementación por defecto de scene(_:willConnectTo:options:) crea su vista de contenido inicial (ContentView si está utilizando SwiftUI), crea un nuevo UIWindow, establece el rootViewController de la ventana y hace que esta ventana sea la ventana clave. Puedes pensar en esta ventana como la ventana que tu usuario ve. Esto, por desgracia, no es el caso. Las ventanas han existido desde antes de iOS 13 y representan la ventana gráfica en la que opera tu aplicación. Por lo tanto, la UISceneSession controla la ventana visible que ve el usuario, la UIWindow que creas es la vista contenedora de tu aplicación.

Además de configurar las vistas iniciales, puedes usar scene(_:willConnectTo:options:) para restaurar la UI de tu escena en caso de que ésta se haya desconectado en el pasado. Por ejemplo, porque fue enviada al fondo. También puedes leer el objeto connectionOptions para ver si tu escena fue creada debido a una solicitud de HandOff o quizás para abrir una URL. Le mostraré cómo hacer esto más adelante en esta entrada del blog.

Una vez que su escena se ha conectado, el siguiente método en el ciclo de vida de su escena es sceneWillEnterForeground(_:). Este método se llama cuando su escena tomará el escenario. Esto podría ser cuando su aplicación transiciones desde el fondo al primer plano, o si es sólo convertirse en activo por primera vez. A continuación, se llama a sceneDidBecomeActive(_:). Este es el punto en el que su escena se establece, visible y listo para ser utilizado.

Cuando su aplicación va al fondo, sceneWillResignActive(_:) y sceneDidEnterBackground(_:) son llamados. No voy a entrar en estos métodos ahora, ya que su propósito varía para cada aplicación, y los comentarios en la plantilla de Xcode hacen un trabajo bastante bueno de explicar cuando se llaman estos métodos. En realidad, estoy seguro de que usted puede averiguar el momento en que estos métodos se llaman a ti mismo.

Un método más interesante es sceneDidDisconnect(_:). Cada vez que tu escena es enviada a un segundo plano, iOS puede decidir desconectar y borrar tu escena para liberar recursos. Esto no significa que tu aplicación haya sido asesinada o que ya no se esté ejecutando, simplemente significa que la escena pasada a este método ya no está activa y se desconectará de su sesión.

Ten en cuenta que la sesión en sí no se descarta necesariamente también, iOS podría decidir volver a conectar una escena a una sesión de escena en cualquier momento, por ejemplo, cuando un usuario trae una escena particular al primer plano de nuevo.

Lo más importante que hay que hacer en sceneDidDisconnect(_:) es descartar cualquier recurso que no necesites mantener alrededor. Esto podría ser datos que se cargan fácilmente desde el disco o la red u otros datos que puede volver a crear fácilmente. También es importante asegurarse de retener los datos que no pueden ser fácilmente recreados, como por ejemplo cualquier entrada que el usuario proporcionó en una escena que se espera que todavía esté allí cuando regresan a una escena.

Considere una aplicación de procesamiento de texto que soporta múltiples escenas. Si un usuario está trabajando en una escena, y luego la cierra para hacer una investigación en Safari y cambiar su música en Spotify, esperaría absolutamente que todo su trabajo siga existiendo en la app de procesamiento de texto, aunque iOS haya desconectado la escena de la app de procesamiento de texto por un tiempo. Para lograr esto, la aplicación debe retener los datos necesarios, y debe codificar el estado actual de la aplicación en un objeto NSUserActivity que puede ser leído más tarde en scene(_:willConnectTo:options:) cuando la escena se vuelve a conectar.

Dado que este flujo de trabajo de conexión, desconexión y reconexión de escenas va a separar las buenas aplicaciones de las grandes, vamos a echar un vistazo a cómo puedes implementar la restauración del estado en tu aplicación.

Realizar una configuración adicional de la escena

Hay varias razones para que tengas que realizar una configuración adicional cuando se configura una escena. Puede que tengas que abrir una URL, manejar una solicitud de Handoff o restaurar el estado. En esta sección, me centraré principalmente en la restauración del estado, ya que es posiblemente el escenario más complejo que podría tener que manejar.

La restauración del estado comienza cuando su escena se desconecta y sceneDidDisconnect(_:) se llama. En este punto, es importante que su aplicación ya tiene un estado establecido que puede ser restaurado más tarde. La mejor manera de hacer esto es utilizar NSUserActivity en su aplicación. Si usas NSUserActivity para soportar Handoff, Atajos de Siri, indexación de Spotlight y más, no tienes mucho trabajo extra que hacer. Si todavía no usas NSUserActivity, no te preocupes. Una actividad de usuario sencilla podría tener el siguiente aspecto:

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

Nótese que esta actividad de usuario no está estructurada como Apple recomienda, es un ejemplo muy escueto que pretende ilustrar la restauración de estados. Para una guía completa sobre NSUserActivity, le recomiendo que eche un vistazo a la documentación de Apple sobre este tema.

Cuando llega el momento de proporcionar una actividad de usuario que puede ser restaurada en un momento posterior, el sistema llama al método stateRestorationActivity(for:) en su SceneDelegate. Tenga en cuenta que este método no es parte de la plantilla predeterminada

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

Hacer esto asocia la actividad de usuario actualmente activa para una escena con la sesión de la escena. Recuerde que cada vez que una escena se desconecta, el UISceneSession que posee el UIScene no se descarta para permitir que la sesión se vuelva a conectar a una escena. Cuando esto ocurre, se llama de nuevo a scene(_:willConnectTo:options:). En este método, usted tiene acceso a la UISceneSession que posee el UIScene para que pueda leer el stateRestorationActivity de la sesión y restaurar el estado de la aplicación, según sea necesario:

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}

Por supuesto, los detalles finos de este código variará en función de su aplicación, pero la idea general debe ser clara.

Si se espera que tu UISceneSession maneje una URL, puedes inspeccionar el urlContexts del objeto connectionOptions para encontrar las URLs que tu escena debe abrir y la información sobre cómo tu aplicación debe hacerlo:

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

El objeto options contendrá información sobre si tu escena debe abrir la URL en su lugar, qué aplicación solicitó que se abriera esta URL y otros metadatos sobre la solicitud.

Los fundamentos de la restauración de estado en iOS 13 con el SceneDelegate son sorprendentemente sencillos, sobre todo porque se basa en NSUserActivity, lo que significa que muchas aplicaciones no tendrán que hacer demasiado trabajo para empezar a soportar la restauración de estado para sus escenas.

Tenga en cuenta que si desea tener soporte para múltiples escenas para su aplicación en iPadOS, la restauración de la escena es especialmente importante ya que iOS podría desconectar y volver a conectar sus escenas cuando pasan del primer plano al fondo y viceversa. Especialmente si su aplicación permite a un usuario crear o manipular objetos en una escena, un usuario no esperaría que su trabajo desaparezca si mueve una escena al fondo por un momento.

En resumen

En esta entrada del blog, usted ha aprendido mucho. Has aprendido qué funciones cumplen los AppDelegate y SceneDelegate en iOS 13 y cómo son sus ciclos de vida. Ahora sabes que el AppDelegate es responsable de reaccionar a los eventos a nivel de aplicación, como el lanzamiento de la aplicación, por ejemplo. El SceneDelegate es responsable de los eventos relacionados con el ciclo de vida de la escena. Por ejemplo, la creación de la escena, la destrucción y la restauración del estado de un UISceneSession. En otras palabras, la principal razón por la que Apple añadió UISceneDelegate a iOS 13 fue para crear un buen punto de entrada para las aplicaciones multiventana.

Después de conocer los fundamentos de UISceneDelegate, has visto un ejemplo muy sencillo de cómo es la restauración de estado en iOS 13 con UISceneSession y UIScene. Por supuesto, hay mucho más que aprender acerca de cómo su aplicación se comporta cuando un usuario engendra múltiples UISceneSession para su aplicación, y cómo estas escenas pueden tener que permanecer en la sincronización o compartir datos.

Si desea obtener más información sobre el apoyo a múltiples ventanas para su aplicación de iPad (o su aplicación de macOS), asegúrese de revisar mi post Añadir soporte para múltiples ventanas a su aplicación de iPadOS. Gracias por leer, y no dudes en contactarme en Twitter si tienes alguna pregunta o comentario para mí.

Mantente al día con mi boletín semanal

Practical Combine

Aprende todo lo que necesitas saber sobre Combine y cómo puedes usarlo en tus proyectos con mi nuevo libro Practical Combine. Obtendrás trece capítulos, un Playground y un puñado de proyectos de ejemplo que te ayudarán a ponerte en marcha con Combine lo antes posible.

¡El libro está disponible como descarga digital por sólo 29,99 dólares!

Consigue Practical Combine

Deja una respuesta

Tu dirección de correo electrónico no será publicada.