Un tutorial sobre las extensiones de la aplicación iOS 8
Publicado: 2022-03-11Pocos lo habían intentado antes (echa un vistazo a esto), pero fue Apple con el primer iPhone quien definió cómo debía verse 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 establecen estándares sobre cómo debe funcionar un sistema operativo móvil y cómo deben crearse las aplicaciones de un teléfono inteligente.
Construir muros de hormigón entre las aplicaciones, haciéndolas completamente aisladas y sin conocimiento entre ellas, fue el mejor método para mantenerlas seguras y proteger sus datos. Todas las actividades fueron monitoreadas de cerca por iOS, y solo hubo un puñado de acciones que una aplicación podría haber hecho fuera de su alcance.
“¡La abstinencia es la mejor protección!” - ¿Pero, dónde está la diversión en eso?
Les tomó un tiempo; demasiado tiempo si me preguntas, pero con iOS 8 Apple decidió divertirse un poco. iOS 8 introdujo un nuevo concepto llamado Extensiones de aplicación. Esta nueva característica no derribó los muros entre las aplicaciones, pero abrió algunas puertas proporcionando un contacto suave pero tangible entre algunas aplicaciones. La última actualización les dio a los desarrolladores de iOS la opción de personalizar el ecosistema de iOS, y estamos ansiosos por ver que este camino se abra también.
¿Qué son las extensiones de la aplicación iOS 8 y cómo funcionan?
En términos simples, iOS 8 App Extensions brinda un nuevo método para interactuar con su aplicación, sin iniciarla ni mostrarla en la pantalla.
Como era de esperar, Apple se aseguró de estar al tanto de todo, por lo que solo hay 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 realizar tareas rápidas.
- Compartir: una extensión que permite que su aplicación comparta contenido con usuarios en 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 que los usuarios puedan ver o transformar el contenido que se origina en una aplicación host.
- Edición de fotos: una extensión que permite a los usuarios editar una foto o un video dentro de la aplicación Fotos.
- Proveedor de documentos: una extensión utilizada para permitir que otras aplicaciones accedan a los documentos administrados por su aplicación.
- Teclado personalizado: una extensión que reemplaza el teclado del sistema.
Las extensiones de aplicación no son aplicaciones independientes. Proporcionan una funcionalidad ampliada de la aplicación (a la que se puede acceder desde otras aplicaciones, llamadas aplicaciones host) que pretende ser eficiente y enfocada en una sola tarea. Tienen su propio binario, su propia firma de código y su propio conjunto de elementos, pero se entregan a través de 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, estarán disponibles en iOS.
Veamos un ejemplo: un usuario encuentra una imagen usando Safari, presiona el botón de compartir y elige la extensión de su aplicación para compartir. Safari "habla" con el marco social de iOS, que carga y presenta la extensión. El código de la extensión se ejecuta, pasa datos utilizando los canales de comunicación instanciados del sistema y, una vez que se realiza la tarea, Safari elimina la vista de la extensión. Poco después de esto, el sistema finaliza el proceso y su aplicación nunca se mostró en la pantalla. Sin embargo, completó una función para compartir imágenes.
iOS, mediante la comunicación entre procesos, es el responsable de garantizar que la aplicación host y una extensión de la aplicación puedan funcionar juntas. Los desarrolladores usan 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 vital
Las extensiones de aplicación tienen un ciclo de vida diferente al de las aplicaciones de iOS. La aplicación host inicia el ciclo de vida de la extensión como respuesta a la acción de un usuario. Luego, el sistema instancia la extensión de la aplicación y establece un canal de comunicación entre ellos. La vista de la extensión se muestra dentro del contexto de la aplicación host utilizando los elementos recibidos en la solicitud de la aplicación host. 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 host realizando o cancelando inmediatamente la tarea o, si es necesario, iniciando un proceso en segundo plano para realizarla. Inmediatamente después de eso, la aplicación host elimina la vista de la extensión y el usuario vuelve a su contexto anterior dentro de la aplicación host. Los resultados de realizar este proceso podrían devolverse a la aplicación host una vez que se complete el proceso. La extensión generalmente finaliza poco después de que completa la solicitud recibida de la aplicación host (o inicia un proceso en segundo plano para realizarla).
El sistema abre la extensión de la acción de un usuario desde la aplicación host, la extensión muestra la interfaz de usuario, realiza algún trabajo y devuelve datos a la aplicación host (si es apropiado para el tipo de extensión). La aplicación contenedora ni siquiera se ejecuta mientras se ejecuta su extensión.
Creación de una extensión de aplicación: ejemplo práctico con la extensión Today
Las extensiones de Hoy, también llamadas widgets , se encuentran en la vista Hoy del Centro de notificaciones. Son una excelente manera de presentar un contenido actualizado para el usuario (como mostrar las condiciones climáticas) o realizar tareas rápidas (como marcar las cosas hechas en el widget de una aplicación de lista de tareas pendientes). Tengo que señalar aquí que la entrada de teclado no es compatible .
Creemos una extensión Hoy que mostrará la información más actualizada de nuestra aplicación (código en GitHub). Para ejecutar este código, asegúrese de haber (re)configurado el grupo de aplicaciones para el proyecto (seleccione su equipo de desarrollo, tenga en cuenta que el nombre del grupo de aplicaciones debe ser único y siga las instrucciones de Xcode).
Creación de un nuevo widget
Como dijimos antes, las extensiones de aplicación no son aplicaciones independientes. Necesitamos una aplicación contenedora en la que construiremos la extensión de la aplicación. Una vez que tenemos nuestra aplicación contenedora, elegimos agregar un nuevo destino navegando a Archivo -> Nuevo -> Destino en Xcode. Desde aquí elegimos la plantilla para nuestro nuevo objetivo para agregar una Extensión Hoy.
En el siguiente paso podemos elegir nuestro nombre de producto. Ese es el nombre que aparecerá en la vista Hoy del Centro de notificaciones. También hay una opción para elegir el idioma entre Swift y Objective-C en este paso. Al finalizar estos pasos, Xcode crea una plantilla Hoy, que proporciona archivos de implementación y encabezado predeterminados para la clase principal (llamada TodayViewController
) con un archivo Info.plist
y un archivo de interfaz (un guión gráfico o un archivo .xib). El archivo Info.plist
, por defecto, se ve así:
<key>NSExtension</key> <dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.widget-extension</string> </dict>
Si no desea utilizar el guión gráfico proporcionado por la plantilla, elimine la clave NSExtensionMainStoryboard
y agregue la clave NSExtensionPrincipalClass
con el nombre de su controlador de vista como valor.
Un widget de Hoy debería:
- asegurarse de que el contenido siempre se vea actualizado
- responder adecuadamente a las interacciones del usuario
- funcionar bien (los widgets de iOS deben usar la memoria de manera inteligente o serán terminados por el sistema)
Compartir datos y un contenedor compartido
La extensión de la aplicación y su aplicación contenedora tienen acceso a los datos compartidos en su contenedor compartido definido de forma privada, que es una forma de comunicación indirecta entre la aplicación contenedora y la extensión.
¿No te encanta cómo Apple hace que estas cosas sean tan "simples"? :)
Compartir datos a través de NSUserDefaults
es simple y un caso de uso común. De manera predeterminada, la extensión y la aplicación que la contiene usan conjuntos de datos NSUserDefaults
separados y no pueden acceder a los contenedores de cada uno. Para cambiar este comportamiento, iOS introdujo Grupos de aplicaciones . Después de habilitar los grupos de aplicaciones en la aplicación contenedora y la extensión, en lugar de usar [NSUserDefaults standardUserDefaults]
use [[NSUserDefaults alloc] initWithSuiteName:@"group.yourAppGroupName"]
para acceder al mismo contenedor compartido.
Actualización del widget
Para garantizar que el contenido esté siempre actualizado, la extensión Hoy proporciona una API para administrar el estado de un widget y manejar las actualizaciones de contenido. Ocasionalmente, el sistema captura instantáneas de la vista del widget, por lo que cuando el widget se vuelve visible, se muestra la instantánea más reciente hasta que se reemplaza con una versión en vivo de la vista. La conformidad con el protocolo NCWidgetProviding
es importante para actualizar el estado de un widget antes de tomar una instantánea. Una vez que el widget recibe la llamada widgetPerformUpdateWithCompletionHandler:
la vista del widget debe actualizarse con el contenido más reciente y el controlador de finalización debe llamarse con una de las siguientes constantes para describir el resultado de la actualización:

-
NCUpdateResultNewData
: el nuevo contenido requiere volver a dibujar la vista -
NCUpdateResultNoDate
: el widget no requiere actualización -
NCUpdateResultFailed
: se produjo un error durante el proceso de actualización
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { // Perform any setup necessary in order to update the view. // If an error is encountered, use NCUpdateResultFailed // If there's no update required, use NCUpdateResultNoData // If there's an update, use NCUpdateResultNewData [self updateTableView]; completionHandler(NCUpdateResultNewData); }
Controlar cuándo se puede ver el widget
Para controlar cuándo se muestra un widget, use el setHasContent:forWidgetWithBundleIdentifier:
de la clase NCWidgetController
. Este método te permitirá especificar el estado del contenido del widget. Se puede llamar desde el widget o desde la aplicación que lo contiene (si está activa). Puede pasar un indicador NO
o 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 Hoy.
NCWidgetController *widgetController = [[NCWidgetController alloc] init]; [widgetController setHasContent:YES forWidgetWithBundleIdentifier:@"com.your-company.your-app.your-widget"];
Apertura de la aplicación contenedora desde el widget
El widget Hoy es la única extensión que puede solicitar la apertura de la aplicación que lo contiene llamando al openURL:completionHandler:
Para asegurarse de que la aplicación contenedora se abra de una manera que tenga sentido en el contexto de la tarea actual del usuario, se debe definir un esquema de URL personalizado (que tanto el widget como la aplicación contenedora pueden usar).
[self.extensionContext openURL:[NSURL URLWithString:@"customURLsheme://URLpath"] completionHandler:nil];
Consideraciones de la interfaz de usuario
Al diseñar su widget, aproveche la clase UIVisualEffectView
, teniendo en cuenta que las vistas que deben estar borrosas/vibrantes deben agregarse a contentView
y no a UIVisualEffectView
directamente. Los widgets (que se ajustan al protocolo NCWidgetProviding
) deben cargar estados almacenados en caché en viewWillAppear:
para que coincidan con el estado de la vista desde la última viewWillDisappear:
y luego hacer una transición sin problemas a los nuevos datos cuando lleguen, lo cual no es un caso con una vista normal controlador (la interfaz de usuario está configurada en viewDidLoad
y maneja las animaciones y la carga de datos en viewWillAppear
). Los widgets deben estar diseñados para realizar una tarea o abrir la aplicación que los contiene con un solo toque. La entrada de teclado no está disponible dentro de un widget. Esto significa que no se debe utilizar ninguna interfaz de usuario que requiera la entrada de texto.
No es posible agregar pergaminos a un widget, tanto vertical como horizontalmente. O más precisamente, es posible agregar una vista de desplazamiento, pero el desplazamiento no funcionará. El centro de notificaciones interceptará el gesto de desplazamiento horizontal en una vista de desplazamiento en la extensión Hoy, lo que hará que se desplace de Hoy al Centro de notificaciones. El desplazamiento vertical de una vista de desplazamiento dentro de una extensión Hoy se interrumpirá al desplazarse por la Vista de hoy.
Notas técnicas
Aquí señalaré algunas cosas importantes a tener en cuenta al crear una extensión de aplicación.
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 aplicación no pueden acceder a un objeto sharedApplication ni usar 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 la aplicación no pueden acceder a la cámara o al micrófono del dispositivo (pero este no es el caso para todos los elementos de hardware). Esto es el resultado de la falta de disponibilidad de algunas API. Para acceder a algunos elementos de hardware en la extensión de la aplicación, deberá verificar si su API está disponible para las extensiones de la aplicación o no (con la verificación de disponibilidad de la API descrita anteriormente).
La mayoría de las tareas en segundo plano están prohibidas: las extensiones de aplicación no pueden realizar tareas en segundo plano de larga duración, excepto iniciar cargas o descargas, que se analizan a continuación.
AirDrop está fuera de los límites : las extensiones de la aplicación no pueden recibir (pero pueden enviar) datos usando AirDrop.
Cargar/Descargar en segundo plano
La única tarea que se puede realizar en segundo plano es la carga/descarga mediante el NSURLSession object
.
Una vez que se inicia la tarea de carga/descarga, la extensión puede completar la solicitud de la aplicación host y finalizar 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 inicia la aplicación contenedora en segundo plano y se llama al método de delegado de la application:handleEventsForBackgroundURLSession:completionHandler:
La aplicación cuya extensión inicia una tarea NSURLSession
en segundo plano debe tener un contenedor compartido configurado al que puedan acceder tanto la aplicación contenedora como su extensión.
Asegúrese de crear diferentes sesiones en segundo plano para la aplicación contenedora y cada una de sus extensiones de aplicación (cada sesión en segundo plano debe tener un identificador único). Esto es importante porque solo un proceso puede usar una sesión en segundo plano a la vez.
Acción frente a Compartir
Las diferencias entre las extensiones Action y Share no están del todo claras desde la perspectiva de un codificador, porque en la práctica son muy similares. La plantilla de Xcode para el destino de la extensión para compartir usa SLComposeServiceViewController
, que proporciona una interfaz de usuario de vista de redacción estándar que puede usar para compartir en redes sociales, pero no es obligatorio. Una extensión compartida también puede heredar directamente de UIViewController para un diseño completamente 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án en cómo deben usarse. Con la extensión de acción, puede crear 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 host). La extensión Share le permite compartir comentarios, fotos, videos, audio, enlaces y más directamente desde la aplicación host. El UIActivityViewController
maneja las extensiones Action y Share, donde las extensiones Share se presentan como íconos de color en la fila superior y las extensiones de acción se presentan como íconos monocromáticos en la fila inferior (Imagen 2.1).
API prohibidas
Las API marcadas en los archivos de encabezado con la macro NS_EXTENSION_UNAVAILABLE
, o una macro similar para la falta de disponibilidad, no se pueden usar (por ejemplo: los marcos de interfaz de usuario de HealthKit y EventKit en iOS 8 no están disponibles para usar en ninguna extensión de aplicación).
Si está compartiendo código entre una aplicación y una extensión, debe tener en cuenta que incluso hacer referencia a una API que no está permitida para la extensión de la aplicación provocará el rechazo de su aplicación en la App Store. Puede elegir lidiar con esto refactorizando las clases compartidas en jerarquías, con un padre común y diferentes subclases para diferentes objetivos. Otra forma es usar el preprocesador mediante verificaciones #ifdef
. Debido a que todavía no hay un objetivo condicional incorporado, debe crear el suyo propio.
Otra buena manera de hacer esto es creando su propio marco integrado. Solo asegúrese de que no contenga ninguna API que no esté disponible para las extensiones. Para configurar una extensión de aplicación para usar un marco incrustado, navegue a la configuración de compilación del objetivo y establezca la configuración "Requerir solo API segura para extensión de aplicación" en Sí. Al configurar el proyecto Xcode, en la fase de compilación Copiar archivos, se debe elegir "Frameworks" como destino para el marco incrustado. Si elige el destino "SharedFrameworks", App Store rechazará su envío.
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 Pautas de interfaz humana de iOS de Apple cuando diseñe una extensión de aplicación. Debe asegurarse de que la extensión de su aplicación sea universal, independientemente del dispositivo que admita la aplicación que la contiene. Para asegurarse de que la extensión de la aplicación sea universal, use la configuración de compilación de la familia de dispositivos de destino 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 usan iOS 8 (según la medición de la App Store el 13 de abril de 2015), las extensiones de aplicaciones son características increíbles que las aplicaciones deberían aprovechar. Al combinar las restricciones de la API y la forma de compartir datos entre las extensiones y la aplicación que las contiene, parece que Apple logró abordar 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 este es un concepto muy nuevo, parece muy prometedor.