Tutorial sobre iOS 8 App Extensions

Pocos lo habían intentado antes (echa un vistazo a esto), pero fue Apple con el primer iPhone quien definió cómo debía ser un Smartphone y un SO móvil. Apple hizo un avance increíble en hardware y experiencia de usuario. Sin embargo, a menudo olvidamos que también establecieron los estándares de cómo debería funcionar un SO móvil, y cómo deberían hacerse las aplicaciones de un Smartphone.

Construir muros de hormigón entre las aplicaciones, haciendo que estuvieran completamente aisladas y no se conocieran entre sí, era el mejor método para mantenerlas seguras y proteger sus datos. Todas las actividades estaban estrechamente vigiladas por iOS, y sólo había un puñado de acciones que una aplicación podía hacer fuera de su ámbito.

«¡La abstinencia es la mejor protección!» –

Pero ¿dónde está la diversión en eso?

Les llevó un tiempo; demasiado si me preguntas, pero con iOS 8 Apple decidió divertirse un poco. iOS 8 introdujo un nuevo concepto llamado App Extensions. Esta nueva característica no derribó los muros entre las aplicaciones, pero abrió algunas puertas proporcionando un contacto suave pero tangible entre algunas apps. La última actualización dio a los desarrolladores de iOS una opción para personalizar el ecosistema de iOS, y estamos ansiosos por ver cómo se abre este camino también.

¿Qué son las App Extensions de iOS 8 y cómo funcionan?

En términos sencillos, las App Extensions de iOS 8 proporcionan un nuevo método para interactuar con tu aplicación, sin necesidad de iniciarla o mostrarla en la pantalla.

Como era de esperar, Apple se aseguró de estar al tanto de todo, por lo que hay sólo un puñado de nuevos puntos de entrada que su aplicación puede proporcionar:

  • Hoy (también llamado widget) – una extensión que se muestra en la vista Hoy del Centro de Notificaciones muestra información breve y permite la realización de tareas rápidas.
  • Compartir – una extensión que permite a su aplicación compartir contenido con los usuarios en las redes sociales y otros servicios para compartir.
  • Acción – una extensión que permite crear botones de acción personalizados en la hoja de Acción para permitir a los usuarios ver o transformar el contenido que se origina en una aplicación anfitriona.
  • Edición de fotos – una extensión que permite a los usuarios editar una foto o un vídeo dentro de la app de Fotos.
  • Proveedor de documentos – una extensión utilizada para permitir que otras apps accedan a los documentos gestionados por tu app.
  • Teclado personalizado – una extensión que sustituye al teclado del sistema.

Las extensiones de la app no son apps independientes. Proporcionan una funcionalidad extendida de la aplicación (a la que se puede acceder desde otras aplicaciones, llamadas aplicaciones anfitrionas) que está pensada para ser eficiente y enfocada hacia una única tarea. Tienen su propio binario, su propia firma de código y su propio conjunto de elementos, pero se entregan a través de la App Store como parte del binario de la aplicación contenedora. Una aplicación (contenedora) puede tener más de una extensión. Una vez que el usuario instala una aplicación que tiene extensiones, éstas estarán disponibles en todo iOS.

Veamos un ejemplo: Un usuario encuentra una imagen utilizando Safari, pulsa el botón de compartir y elige la extensión de su aplicación para compartirla. Safari «habla» con el framework social de iOS, que carga y presenta la extensión. El código de la extensión se ejecuta, pasa los datos utilizando los canales de comunicación instanciados del sistema y, una vez terminada la tarea, Safari cierra la vista de la extensión. Poco después, el sistema termina el proceso, y su aplicación nunca se mostró en la pantalla. Sin embargo, completó una función de intercambio de imágenes.

iOS, utilizando la comunicación entre procesos, es el responsable de garantizar que la aplicación anfitriona y una extensión de la aplicación pueden trabajar juntos. Los desarrolladores utilizan las API de alto nivel proporcionadas por el punto de extensión y el sistema, por lo que nunca tienen que preocuparse por los mecanismos de comunicación subyacentes.

Ciclo de vida

Las extensiones de app tienen un ciclo de vida diferente al de las apps de iOS. La aplicación anfitriona inicia el ciclo de vida de la extensión como respuesta a una acción del usuario. A continuación, el sistema instala la extensión de la aplicación y establece un canal de comunicación entre ellos. La vista de la extensión se muestra en el contexto de la aplicación anfitriona utilizando los elementos recibidos en la solicitud de la aplicación anfitriona. Una vez que se muestra la vista de la extensión, el usuario puede interactuar con ella. En respuesta a la acción del usuario, la extensión completa la solicitud de la aplicación anfitriona realizando/cancelando inmediatamente la tarea o, si es necesario, iniciando un proceso en segundo plano para realizarla. Inmediatamente después, la aplicación anfitriona cierra la vista de la extensión y el usuario vuelve a su contexto anterior dentro de la aplicación anfitriona. Los resultados de la realización de este proceso podrían ser devueltos a la aplicación anfitriona una vez que el proceso se haya completado. La extensión suele terminar poco después de completar la solicitud recibida de la app anfitriona (o inicia un proceso en segundo plano para realizarla).

El sistema abre la extensión de una acción del usuario desde la app anfitriona, la extensión muestra la UI, realiza algún trabajo y devuelve los datos a la app anfitriona (si eso es apropiado para el tipo de extensión). La aplicación que la contiene ni siquiera se ejecuta mientras su extensión se está ejecutando.

Creación de una extensión de aplicación – Ejemplo práctico utilizando la extensión Today

Las extensiones Today, también llamadas widgets, se encuentran en la vista Today del centro de notificaciones. Son una gran manera de presentar un contenido actualizado para el usuario (como mostrar las condiciones meteorológicas) o realizar tareas rápidas (como marcar las cosas hechas en el widget de una aplicación de lista de tareas). Tengo que señalar aquí que la entrada por teclado no está soportada.

Creemos una extensión Today que mostrará la información más actualizada de nuestra app (código en GitHub). Para poder ejecutar este código, asegúrate de haber (re)configurado el App Group para el proyecto (selecciona tu Equipo de Desarrollo, ten en cuenta que el nombre del App Group tiene que ser único y sigue las instrucciones de Xcode).

Creando un nuevo Widget

Como hemos dicho antes, las extensiones de app no son apps independientes. Necesitamos una app contenedora sobre la que construiremos la extensión de la app. Una vez que tenemos nuestra app contenedora, elegimos añadir un nuevo objetivo navegando a Archivo -> Nuevo -> Objetivo en Xcode. Desde aquí elegimos la plantilla de nuestro nuevo objetivo para añadir una Extensión de Hoy.

En el siguiente paso podemos elegir nuestro Nombre de Producto. Ese es el nombre que aparecerá en la vista Today del Centro de Notificaciones. En este paso también hay una opción para elegir el lenguaje entre Swift y Objective-C. Al terminar estos pasos, Xcode crea una plantilla de Today, que proporciona archivos de cabecera e implementación por defecto para la clase principal (llamada TodayViewController) con el archivo Info.plist y un archivo de interfaz (un archivo storyboard o .xib). El archivo Info.plist, por defecto, tiene el siguiente aspecto:

Si no quieres utilizar el storyboard proporcionado por la plantilla, elimina la clave NSExtensionMainStoryboard y añade la clave NSExtensionPrincipalClass con el nombre de tu controlador de vista como valor.

Un widget de Hoy debería:

  • asegurar que el contenido siempre se vea actualizado
  • responder apropiadamente a las interacciones del usuario
  • performar bien (los widgets de iOS deben usar la memoria sabiamente o serán terminados por el sistema)

Compartir datos y un contenedor compartido

La extensión de la app y su app contenedora tienen ambas acceso a los datos compartidos en su contenedor compartido definido privadamente -. que es una forma de una comunicación indirecta entre la app contenedora y la extensión.

¿No te encanta cómo Apple hace estas cosas tan «simples»? 🙂

Compartir datos a través de NSUserDefaults es simple y un caso de uso común. Por defecto, la extensión y la aplicación que la contiene utilizan conjuntos de datos NSUserDefaults separados, y no pueden acceder a los contenedores del otro. Para cambiar este comportamiento, iOS introdujo los grupos de aplicaciones. Después de habilitar los grupos de aplicaciones en la app contenedora y en la extensión, en lugar de utilizar usa initWithSuiteName:@"group.yourAppGroupName"] para acceder al mismo contenedor compartido.

Actualización del widget

Para garantizar que el contenido esté siempre actualizado, la extensión Today proporciona una API para gestionar el estado de un widget y manejar las actualizaciones de contenido. El sistema captura ocasionalmente instantáneas de la vista del widget, de modo que cuando el widget se hace visible, se muestra la instantánea más reciente hasta que se sustituye por una versión viva de la vista. Una conformación al protocolo NCWidgetProviding es importante para actualizar el estado de un widget antes de que se tome una instantánea. Una vez que el widget recibe la llamada widgetPerformUpdateWithCompletionHandler:, la vista del widget debe ser actualizada con el contenido más reciente y el manejador de finalización debe ser llamado con una de las siguientes constantes para describir el resultado de la actualización:

  • NCUpdateResultNewData – El nuevo contenido requiere redibujar la vista
  • NCUpdateResultNoDate – El widget no requiere ser actualizado
  • NCUpdateResultFailed – Se ha producido un error durante el proceso de actualización

Controlar cuándo se puede ver el widget

Para controlar cuándo se muestra un widget, utiliza el método setHasContent:forWidgetWithBundleIdentifier: de la clase NCWidgetController. Este método le permitirá especificar el estado del contenido del widget. Puede ser llamado desde el widget o desde su aplicación contenedora (si está activa). Puedes pasar una bandera NO o una bandera YES a este método, definiendo que el contenido del widget está listo o no. Si el contenido no está listo, iOS no mostrará su widget cuando se abra la vista Today.

Abrir la app contenedora desde el widget

El widget Today es la única extensión que puede solicitar abrir su app contenedora llamando al método openURL:completionHandler:. Para asegurar que la app contenedora se abra de una manera que tenga sentido en el contexto de la tarea actual del usuario, debe definirse un esquema de URL personalizado (que tanto el widget como la app contenedora puedan utilizar).

 completionHandler:nil];

Consideraciones sobre la interfaz de usuario

Cuando diseñe su widget, aproveche la clase UIVisualEffectView, teniendo en cuenta que las vistas que deben ser borrosas/vibrantes deben añadirse a la clase contentView y no a la UIVisualEffectView directamente. Los widgets (que se ajustan al protocolo NCWidgetProviding) deben cargar los estados en caché en viewWillAppear: para que coincidan con el estado de la vista del último viewWillDisappear: y luego hacer una transición suave a los nuevos datos cuando lleguen, lo que no ocurre con un controlador de vista normal (la UI se configura en viewDidLoad y maneja las animaciones y la carga de datos en viewWillAppear). Los widgets deben estar diseñados para realizar una tarea, o para abrir la aplicación que los contiene con un solo toque. La entrada del teclado no está disponible dentro de un widget. Esto significa que cualquier UI que requiera la entrada de texto no debe ser utilizado.

Añadir scrolls en un widget, tanto vertical como horizontal, no es posible. O más precisamente, añadir una vista de desplazamiento es posible pero el desplazamiento no funcionará. El gesto de desplazamiento horizontal en una vista de desplazamiento en la extensión de Hoy será interceptado por el centro de notificaciones, lo que provocará el desplazamiento de Hoy al centro de notificaciones. El desplazamiento vertical de una vista de desplazamiento dentro de una extensión de Hoy será interrumpido por el desplazamiento de la vista de Hoy.

Notas técnicas

Aquí señalaré algunas cosas importantes a tener en cuenta al crear una extensión de App.

Características comunes a todas las extensiones

Los siguientes elementos son verdaderos para todas las extensiones:

  • El objeto sharedApplication está fuera de los límites: Las extensiones de la app no pueden acceder a un objeto sharedApplication, ni utilizar ninguno de los métodos relacionados con ese objeto.

  • La cámara y el micrófono están fuera de los límites: Las extensiones de las apps no pueden acceder a la cámara o al micrófono del dispositivo (pero no es un caso para todos los elementos de hardware). Esto es el resultado de la indisponibilidad de algunas APIs. Para acceder a algunos elementos de hardware en la extensión de la app, tendrás que comprobar si su API está disponible para las extensiones de la app o no (con la comprobación de disponibilidad de la API descrita anteriormente).

  • La mayoría de las tareas en segundo plano están fuera de los límites: Las extensiones de aplicaciones no pueden realizar tareas en segundo plano de larga duración, excepto iniciar cargas o descargas, lo que se discute más adelante.

  • AirDrop está fuera de los límites: Las extensiones de aplicaciones no pueden recibir (pero sí enviar) datos utilizando AirDrop.

Carga/Descarga en segundo plano

La única tarea que se puede realizar en segundo plano es la carga/descarga, utilizando el NSURLSession object.

Después de que se inicie la tarea de carga/descarga, la extensión puede completar la solicitud de la aplicación anfitriona y ser terminada sin ningún efecto en el resultado de la tarea. Si la extensión no se está ejecutando en el momento en que se completa la tarea en segundo plano, el sistema lanza la app contenedora en segundo plano y se llama al método delegado de la app application:handleEventsForBackgroundURLSession:completionHandler:.

La app cuya extensión inicia una tarea en segundo plano NSURLSession debe tener un contenedor compartido configurado al que puedan acceder tanto la app contenedora como su extensión.

Asegúrese de crear diferentes sesiones en segundo plano para la app contenedora y cada una de sus extensiones de app (cada sesión en segundo plano debe tener un identificador único). Esto es importante porque sólo un proceso puede utilizar una sesión de fondo a la vez.

Action vs. Share

Las diferencias entre las extensiones Action y Share no están completamente claras desde la perspectiva de un codificador, porque en la práctica son muy similares. La plantilla de Xcode para el objetivo de la extensión Share utiliza SLComposeServiceViewController, que proporciona una interfaz de usuario de vista de composición estándar que se puede utilizar para el intercambio social, pero no es necesario. Una extensión de compartir también puede heredar directamente de UIViewController para un diseño totalmente personalizado, de la misma manera que una extensión de Acción puede heredar de SLComposeServiceViewController.

Las diferencias entre estos dos tipos de extensiones está en cómo están destinados a ser utilizados. Con la extensión Action, puedes construir una extensión sin interfaz de usuario propia (por ejemplo, una extensión utilizada para traducir el texto seleccionado y devolver la traducción a la aplicación anfitriona). La extensión Share te permite compartir comentarios, fotos, vídeos, audio, enlaces y mucho más directamente desde la aplicación anfitriona. El UIActivityViewController maneja tanto las extensiones Action como Share, donde las extensiones Share se presentan como iconos de color en la fila superior y las extensiones Action se presentan como iconos monocromáticos en la fila inferior (Imagen 2.1).

APIs prohibidas

Las APIs marcadas en los archivos de cabecera con la macro NS_EXTENSION_UNAVAILABLE, o macro similar de no disponibilidad, no pueden ser utilizadas (por ejemplo: HealthKit y EventKit UI frameworks en iOS 8 no están disponibles para su uso en cualquier extensión de la app).

Si estás compartiendo código entre una app y una extensión, tienes que tener en cuenta que incluso hacer referencia a una API que no está permitida para la extensión de la app conducirá al rechazo de tu app desde la App Store. Usted puede elegir para hacer frente a esto mediante la refactorización de las clases compartidas en jerarquías, con un padre común y diferentes subclases para diferentes targets.Another manera es utilizar el preprocesador por #ifdef cheques. Debido a que todavía no hay condicional objetivo incorporado, usted tiene que crear su propio.

Otra buena manera de hacer esto es mediante la creación de su propio marco incrustado. Sólo asegúrate de que no contenga ninguna API no disponible para las extensiones. Para configurar una extensión de la aplicación para utilizar un marco incrustado, navegue a la configuración de construcción del objetivo y establezca el ajuste «Require Only App-Extension-Safe API» en Yes. Al configurar el proyecto de Xcode, en la fase de compilación de Copiar Archivos, se debe elegir «Frameworks» como destino para el framework incrustado. Si elige el destino «SharedFrameworks», su envío será rechazado por la App Store.

Una nota sobre la compatibilidad con versiones anteriores

Aunque las extensiones de aplicaciones solo han estado disponibles desde iOS 8, puede hacer que su aplicación contenedora esté disponible para las versiones anteriores de iOS.

Cumplimiento de la interfaz humana de Apple

Tenga en cuenta las directrices de la interfaz humana de iOS de Apple cuando diseñe una extensión de aplicación. Debes asegurarte de que la extensión de tu app es universal, independientemente del dispositivo que soporte tu app contenedora. Para asegurarse de que la extensión de la aplicación es universal, utilice el ajuste de construcción de la familia de dispositivos objetivo en Xcode especificando el valor «iPhone/iPad» (a veces llamado universal).

Conclusión

Las extensiones de aplicaciones definitivamente tienen el impacto más visible en iOS 8. Dado que el 79% de los dispositivos ya utilizan iOS 8 (según las mediciones de la App Store del 13 de abril de 2015), las extensiones de aplicaciones son características increíbles que las apps deberían aprovechar. Con la combinación de las restricciones de la API y la forma de compartir los datos entre las extensiones y su app contenedora, parece que Apple ha conseguido solucionar una de las mayores quejas sobre la plataforma sin comprometer su modelo de seguridad. Todavía no hay forma de que las aplicaciones de terceros compartan directamente sus datos entre sí. Aunque se trata de un concepto muy nuevo, parece muy prometedor.

Deja una respuesta

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