WidgetKit para iOS 14: todo lo que necesitas saber

Apple presentó en la WWDC 2020 el nuevo framework WidgetKit disponible para iOS 14, macOS 11.0 y Mac Catalyst 14.0. Es decir, para poder trabajar con WidgetKit será necesario usar Xcode 12 o superior y solo pueden ser desarrollados usando el nuevo framework SwiftUI.

Hasta ahora, los widgets se mostraban únicamente en la pantalla 'Today'. Con este nuevo framework, se han rediseñado los widgets para dar más información; ahora además se podrán añadir a la pantalla de 'Inicio de iOS' o en el 'Centro de notificaciones' de macOS, eligiendo distintos tamaños y colocándolos donde se quiera.

En este post te contaremos todo lo que necesitas saber sobre WidgetKit para iOS 14. ¡Empezamos!

 

 

Propósito

Un widget eleva el contenido clave de una aplicación y lo muestra donde los usuarios puedan verlo de un vistazo en iPhone, iPad y Mac.

Los widgets en iOS deben diseñarse teniendo en cuenta tres aspectos:

  • Visibilidad
  • Relevancia
  • Personalización

Aunque los usuarios pueden interactuar con un widget para ver o hacer más en la aplicación, el propósito principal de un widget es mostrar una pequeña cantidad de información oportuna y personalmente relevante que los usuarios pueden ver sin abrir la aplicación. Cuando necesiten más detalle, el widget los llevará directamente al lugar apropiado dentro la aplicación.

Identificar una idea única y definir el alcance de la información que se mostrará son los primeros pasos cruciales en el proceso de diseño de un widget.

Un widget debe enfocarse en una idea. En la mayoría de los casos, se puede utilizar la idea principal de la aplicación como la idea del widget. Pero también puede funcionar bien centrarse en una parte de la aplicación.

El usuario podrá personalizar la pantalla de 'Inicio' de iOS o en el 'Centro de notificaciones' de macOS añadiendo estos nuevos widgets. Aunque también seguirán teniendo la posibilidad de añadir widgets como hasta ahora, mostrándose en la pantalla 'Today'.

La selección de los widgets se realiza desde la galería de widgets, donde también pueden elegir su tamaño. Después de seleccionar un widget, los usuarios pueden mover el widget a su ubicación preferida y, en muchos casos, configurarlo.

Para que el widget de una aplicación aparezca en la galería de widgets, el usuario debe iniciar la aplicación que contiene el widget al menos una vez después de instalarla.

El usuario también podrá crear un Grupo inteligente (Smart Stack) para modificar automáticamente el orden de los widgets, ver la información más pertinente según la hora del día y consultar el widget que quiera fácilmente.

 

Responder a las interacciones del usuario

Cuando los usuarios interactúan con un widget, el sistema inicia la aplicación para manejar la solicitud. Cuando el sistema activa la aplicación, esta navega hasta los detalles correspondientes al contenido del widget.

Todo el widget responde al ser pulsado y se puede activar un deeplink si es necesario. Para los widgets medianos y grandes se pueden establecer varios objetos táctiles, incluido un enlace deeplink.

 

Limitaciones

Los widgets presentan información de solo lectura y no son compatibles con elementos interactivos como elementos con scroll o switches. WidgetKit omite elementos interactivos al representar el contenido de un widget.

Es importante tener en cuenta que los widgets no soportan visualización de vídeos ni imágenes animadas.

 

 

Visión general

Con WidgetKit se puede crear un widget basado en dos diferente tipos de configuración:

  • StaticConfiguration. Deberá usarse cuando el widget no necesite configuración por parte del usuario para mostrar un contenido.
  • IntentConfiguration. Deberá usarse cuando sí se necesite una configuración por parte del usuario para mostrar un contenido específico

Es decir, los widgets podrán ofrecer el mismo tipo de contenido siempre o permitir a los usuarios personalizar cada widget con el tipo de contenido que deseen.

En cuanto al tamaño, existen tres estilos diferentes para los widgets: pequeños, medianos y grandes. Por defecto, todos los estilos de tamaño están habilitados, pero se pueden deshabilitar.

Apple usa un tamaño de widget extra grande para Apple News, pero este tamaño no está disponible para desarrolladores.

También tienes que saber que los widgets admiten el modo oscuro por defecto.

 

Actualización de datos

Los desarrolladores no tienen el control de actualización de datos en los widgets. Cuando un widget se muestre en el dispositivo con más frecuencia, existirá más posibilidades de refresco de contenido.

La actualización de datos se puede activar mediante:

  • Una notificación silenciosa en segundo plano, Pushing Background Updates.
  • Una configuración de Timeline. Configurar el widget con un Timeline Provider, y usar las vistas SwiftUI para mostrar el contenido del widget. El Timeline Provider le dice a WidgetKit cuándo actualizar el contenido del widget.

"WidgetKit. Configuración de Timeline con Timeline Provider"

  • Una acción basada en la aplicación: App-based action.

Otro aspecto muy importante a tener en cuenta para la actualización de datos será, si la carga es asíncrona, mostrar un placeholder:

"WidgetKit: placeholder en calendar"

 

Si el widget tiene activada la capacidad Data Protection, WidgetKit representa al widget como un placeholder cuando el derecho de protección de datos especifica los siguientes valores y se cumple la condición asociada: NSFileProtectionComplete o NSFileProtectionCompleteUnlessOpen, y el dispositivo bloqueado. O, NSFileProtectionCompleteUntilFirstUserAuthentication, y el usuario no está autenticado.

 

Pruebas QA

Debido a que los widgets reciben una cantidad limitada de actualizaciones todos los días, Apple recomienda probar el widget durante varios días.

 

 

Diseño

A continuación encontrarás los principales aspectos que deberás tener en cuenta en el diseño de un widget.

 

Creación de un widget útil

Las recomendaciones para diseñar un widget útil son:

  • En cada tamaño mostrar solo la información que esté directamente relacionada con la idea del widget.
  • Evitar crear un widget que no haga nada más que iniciar la aplicación.
  • Ofrecer un widget en varios tamaños proporcionando más información en cada tamaño.
  • Ofrecer un widget con información dinámica que cambie a lo largo del día.
  • Buscar oportunidades para sorprender al usuario.

 

Dar soporte a la interactividad y a la configuración de widgets

Aunque los usuarios pueden tocar un widget para ver o hacer más en la aplicación, el propósito principal de un widget es mostrar una pequeña cantidad de información oportuna y personalmente relevante sin abrir la aplicación.

Los primeros pasos cruciales en el proceso de diseño son identificar una idea única y determinar el alcance de la información que se mostrará.

Apple recomienda:

  • Ofrecer un widget configurable cuando tenga sentido
  • Evitar definir demasiados objetivos táctiles
  • Indicar al usuario cuando la autenticación en la aplicación ofrece valor al widget

 

Actualización de contenido de widget

Los principales aspectos a tener en cuenta para la actualización de contenidos son:

  • Mantener el widget actualizado
  • Dejar que el sistema actualice fechas y horas en el widget
  • Mostrar contenido rápidamente

 

Diseñar un widget

Respecto a su interfaz gráfica, Apple recomienda los diseñadores:

  • Comunicar la marca a través del color, la tipografía y las imágenes
  • Mostrar una cantidad de información cómoda para el usuario
  • Prudencia en el uso de colores
  • Soporte al modo oscuro
  • Considerar utilizar SF Pro
  • Utilizar siempre elementos de texto para asegurar de que dicho texto se adapte bien
  • Considerar el uso de símbolos SF (SF Symbols)
  • Diseñar una vista preliminar realista para mostrar en la galería de widgets
  • Diseñar contenido placeholder que ayude a los usuarios recocer el widget
  • Especificar una descripción del widget
  • Utilizar los márgenes estándar para asegurar la lectura fácil del contenido

 

 

Creación de widgets

La plantilla de extensión de widget proporciona un punto de partida para crear un widget. Una sola extensión de widget puede contener varios tipos de widgets.

Aunque se recomienda incluir todos los widgets en una sola extensión de widget, se pueden agregar varias extensiones si es necesario. Para estos casos, aparecerán todos los widgets al usuario en la pantalla de 'Añadir widget'.

Si un widget necesita el permiso de localización, Apple indica aislar ese widget a una extensión independiente.

 

Añadir un Widget Target a la aplicación

Pasos a seguir para crear un widget:

  1. Abrir en el proyecto File > New > Target
  2. En 'Application Extension group', seleccionar 'Widget Extension, y pulsar 'Next'.
    "WidgetKit. Creación de un widget: widget extension"
  3. Introducir el nombre de su extensión. Si el widget proporciona propiedades configurables por el usuario, marcar la casilla de 'Include Configuration Intent'. Y pulsar Finish.
    "WidgetKit. Creación de un widget: opciones widget target"
  4. Activar el nuevo esquema
    "WidgetKit. Creación de un widget: activar nuevo esquema"

 

Plantilla Widget estático

La plantilla de extensión de un widget estático se compone los siguientes archivos:

  • Info.plist. Definición iOS Target Properties.
  • Assets.xcassets. Definición de recursos:
    • AccentColor. Representa el tinte de la aplicación
    • WidgetBackground. Color background del widget
    • AppIcon. App Icon (se crea por defecto, no se utiliza para los widgets)
  • SampleStaticConfiguration.swift. Implementación de protocolos y clases:
    • TimelineProvider
    • TimelineEntry
    • View
    • Widget
    • PreviewProvider
  • SampleStaticConfigurationExtension.entitlements. Definición de autorización.
     
    
    xml <dict> <key>com.apple.security.app-sandbox</key> <true/> <key>com.apple.security.network.client</key> <true/> </dict>

     

Plantilla Widget dinámico

La plantilla de extensión de un widget dinámico se compone de los archivos:

  • Info.plist. Definición iOS Target Properties.
  • Assets.xcassets. Definición de recursos.
  • SampleIntentConfiguration.swift. Implementación de protocolos y clases
    • IntentTimelineProvider
    • TimelineEntry
    • View
    • Widget
    • PreviewProvider
  • SampleIntentConfiguration.intentdefinition. Definición del Custom Intent, se define la configuración que podrá realizar el usuario sobre el widget.

 

Añadir configuración

La plantilla de extensión de widget proporciona una implementación de widget inicial que se ajusta al protocolo de Widget.

struct EmojiRangerWidget: Widget {
    private let kind: String = "EmojiRangerWidget"

    public var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, 
        intent: DynamicCharacterSelectionIntent.self, 
        provider: Provider()) { entry in
                 EmojiRangerWidgetEntryView(entry: entry)
        }
        .configurationDisplayName(NSLocalizedString("Ranger Detail", comment: ""))
        .description(NSLocalizedString("See your favorite ranger.", comment: ""))
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

 

La propiedad body de este widget determina si el widget tiene propiedades configurables por el usuario. Hay dos tipos de configuraciones disponibles:

  • StaticConfiguration. Para un widget sin propiedades configurables por el usuario. Por ejemplo, un widget de mercado de valores que muestra información general del mercado o un widget de noticias que muestra titulares de tendencias.
  • IntentConfiguration. Para un widget con propiedades configurables por el usuario. Utiliza una Custom intent de SiriKit para definir las propiedades. Por ejemplo, un widget meteorológico que necesita un código postal para una ciudad, o un widget de seguimiento de paquetes que necesita un número de seguimiento.

Cuando se selecciona 'Include Configuration Intent' Xcode utiliza la configuración dinámica del widget.

Para iniciar una de las configuraciones, se debe proporcionar la siguiente información:

  • Kind. Una cadena que identifica el widget. Este es un identificador que debe ser descriptivo de lo que representa el widget.
  • Provider. Un objeto conforme a TimelineProvider que produce una línea de tiempo que le dice a WidgetKit cuándo renderizar el widget. Una línea de tiempo contiene un TimelineEntry personalizado. Las entradas de la línea de tiempo identifican la fecha en la que desea que WidgetKit actualice el contenido del widget. Se debe incluir las propiedades que la vista del widget necesita representar en el tipo personalizado.
  • Content Closure. Un Closure que contiene las vistas de SwiftUI. WidgetKit invoca este bloque para representar el contenido del widget, pasando un parámetro TimelineEntry del proveedor.
  • Custom Intent. Un Intent que define propiedades configurables por el usuario. Para más información puedes consultar en la web oficial Creación de un widget configurable.

Ejemplo de una configuración estática:

var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.mygame.game-status",
            provider: GameStatusProvider(),
        ) { entry in
            GameStatusView(entry.gameStatus)
        }
        .configurationDisplayName("Game Status")
        .description("Shows an overview of your game status")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }

 

Ejemplo de una configuración dinámica:

public var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, 
        intent: DynamicCharacterSelectionIntent.self, 
        provider: Provider()) { entry in
                EmojiRangerWidgetEntryView(entry: entry)
        }
        .configurationDisplayName(NSLocalizedString("Ranger Detail", comment: ""))
        .description(NSLocalizedString("See your favorite ranger.", comment: ""))
        .supportedFamilies([.systemSmall, .systemMedium])
    }

 

Si una aplicación necesita más de un widget, se pueden crear de dos formas: añadiendo todos los widgets al mismo target o creando un target por widget.

El atributo @main es el punto de entrada para la extensión del widget.

Para añadir todos los widgets en el mismo target se define como punto de entrada el WidgetBundle:

@main
struct EmojiRangerBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        EmojiRangerWidget()
        LeaderboardWidget()
    }
}

 

Para definir un único widget en cada target se define como punto de entrada el Widget:

@main
struct SampleIntentConfiguration: Widget {
    let kind: String = "SampleIntentConfiguration"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, 
        intent: ConfigurationIntent.self, 
        provider: Provider()) { entry in
                SampleIntentConfigurationEntryView(entry: entry)
        }
        .configurationDisplayName(NSLocalizedString("Configurable SDOSWidget", 
          comment: ""))
        .description(NSLocalizedString("This is an example SDOSWidget.", 
          comment: ""))
    }
}

 

Proporcionar entradas de la línea de tiempo: Provider

Un TimelineProvider genera las entradas de la línea de tiempo, TimelineEntry, especificando en cada una la fecha y hora para actualizar el contenido del widget.

struct SimpleEntry: TimelineEntry {
    public let date: Date
    let relevance: TimelineEntryRelevance?
    let character: CharacterDetail
}

 

Estado actual de un widget

Para mostrar el widget en la galería de widgets, WidgetKit solicita una instantánea de vista previa. La identificación de esta solicitud de vista previa se comprueba con la propiedad isPreview del parámetro de contexto pasado al método [getSnapshot (in: complete :)].

En el siguiente código, el widget de estado del juego implementa el método de instantánea mostrando un estado vacío si no ha terminado de obtener el estado del servidor:

struct GameStatusProvider: TimelineProvider {
    var hasFetchedGameStatus: Bool
    var gameStatusFromServer: String

    func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) {
        let date = Date()
        let entry: GameStatusEntry

        if context.isPreview && !hasFetchedGameStatus {
            entry = GameStatusEntry(date: date, gameStatus: "—")
        } else {
            entry = GameStatusEntry(date: date, gameStatus: gameStatusFromServer)
        }
        completion(entry)
    }

 

Para los widgets configurables, hay que cumplir con IntentTimelineProvider. Se realizan las mismas funciones que con TimelineProvider, pero incorpora los valores que los usuarios personalizan en el widget [getSnapshot(for:in:completion:)].

 

Serie de estados actuales o futuros de un widget

Después de solicitar la instantánea inicial, WidgetKit llama a [getTimeline(in:completion:)] para solicitar una línea de tiempo regular. La línea de tiempo consta de una o más entradas de línea de tiempo y una política de recarga que informa a WidgetKit cuando solicitar una línea de tiempo posterior.

El siguiente ejemplo muestra cómo el widget de estado del juego genera una línea de tiempo que consta de una sola entrada con el estado actual del juego del servidor y una política de recarga para solicitar una nueva línea de tiempo en 15 minutos:

struct GameStatusProvider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<GameStatusEntry>) -> Void) {
        // Create a timeline entry for "now."
        let date = Date()
        let entry = GameStatusEntry(
            date: date,
            gameStatus: gameStatusFromServer
        )

        // Create a date that's 15 minutes in the future.
        let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: date)!

        // Create the timeline with the entry and a reload policy with the date
        // for the next update.
        let timeline = Timeline(
            entries:[entry],
            policy: .after(nextUpdateDate)
        )

        // Call the completion to pass the timeline to WidgetKit.
        completion(timeline)
    }
}

 

En este ejemplo, si el widget no tenía el estado actual del servidor, podría almacenar una referencia a la finalización, realizar una solicitud asincrónica al servidor para obtener el estado del juego y llamar a la finalización cuando se complete la solicitud.

Para los widgets configurables, ahí que cumplir con IntentTimelineProvider. Se realizan las mismas funciones que con TimelineProvider, pero incorpora los valores que los usuarios personalizan en el widget: [getTimeline(for:in:completion:)].

 

 

Mostrar un placeholder

Cuando WidgetKit muestra un widget por primera vez, muestra la vista del widget como un placeholer. Un placeholder muestra una representación genérica del widget, lo que le da al usuario una idea general de lo que muestra el widget. WidgetKit llama al [placeholder(in:)] para solicitar una entrada que represente la configuración del placeholder del widget. Por ejemplo, el widget de estado del juego implementaría este método de la siguiente manera:

struct GameStatusProvider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        GameStatusEntry(date: Date(), gameStatus: "—")
    }
}

 

Para representar la vista del widget como un placeholder, WidgetKit usa el modificador de vista [redacted(reason:)] que especifica el placeholder del motivo. Para evitar que las vistas en la jerarquía de vistas del widget se representen automáticamente como un placeholder, usar el modificador de vista unredacted().

Si el widget tiene activada la capacidad Data Protection, WidgetKit representa al widget como un placeholder cuando el derecho de protección de datos especifica los siguientes valores y se cumple la condición asociada: NSFileProtectionComplete o NSFileProtectionCompleteUnlessOpen, y el dispositivo bloqueado. O, NSFileProtectionCompleteUntilFirstUserAuthentication, y el usuario no está autenticado.

 

 

Mostrar contenido

Como hemos comentado anteriormente, los widgets definen su contenido usando vistas SwiftUI. Cuando los usuarios añaden un widget desde la galería, eligen la familia específica (pequeña, mediana o grande) de las que admite el widget.

El closure de vistas del widget tiene que ser capaz de representar cada familia compatible con el mismo. WidgetKit establece la familia correspondiente y las propiedades adicionales, como el esquema de color (claro u oscuro), en el entorno SwiftUI.

struct GameStatusView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var gameStatus: GameStatus

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall: GameTurnSummary(gameStatus)
        case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
        case .systemLarge: GameStatusWithStatistics(gameStatus)
        default: GameDetailsNotAvailable()
        }
    }
}

 

En este ejemplo, para cada familia se muestra una vista distinta. La vista declara su body con @ViewBuilder porque varía el tipo de vista que utiliza.

Los widgets presentan información de solo lectura y no son compatibles con elementos interactivos como elementos con scroll o switches. WidgetKit omite elementos interactivos al representar el contenido de un widget.

 

Responder a las interacciones del usuario

Cuando los usuarios interactúan con un widget, el sistema inicia la aplicación para manejar la solicitud. Cuando el sistema activa la aplicación, esta navega hasta los detalles correspondientes al contenido del widget.

El widget puede especificar una URL para informar a la aplicación qué contenido mostrar. Para configurar URL personalizadas en el widget:

  • Para todos los widgets, añadir el modificador de vista [widgetURL(_:)] a una vista en la jerarquía de vistas del widget. Si la jerarquía de vistas del widget incluye más de un modificador widgetURL, el comportamiento no está definido.
  • Para los widgets que usan [systemMedium] o [systemLarge], añadir uno o más controles de [Link] a la jerarquía de vistas del widget. Se pueden utilizar los controles widgetURL y Link.

Por ejemplo, un widget que muestra detalles de un solo personaje en un juego podría usar widgetURL para abrir la aplicación con los detalles de ese personaje:

@ViewBuilder
var body: some View {
    ZStack {
        AvatarView(entry.character)
            .widgetURL(entry.character.url)
            .foregroundColor(.white)
    }
    .background(Color.gameBackground)
}

 

Cuando el widget recibe una interacción, el sistema activa la aplicación que lo contiene y pasa la URL a [onOpenURL(perform:)], [application(_:open:options:)], o 'application(_:open:)', dependiendo del ciclo de vida de su aplicación usos.

 

 

Mantener un widget actualizado

Los widgets usan vistas SwiftUI para mostrar su contenido. WidgetKit muestra estas vistas en un proceso separado. Como resultado, la extensión de widget no está continuamente activa, incluso si el widget está en pantalla.

A pesar de que el widget no siempre está activo, hay varias formas de mantener actualizado su contenido.

 

Generar una línea de tiempo para eventos predecibles

Muchos widgets tienen puntos predecibles en el tiempo en los que tiene sentido actualizar su contenido.

Por ejemplo, un widget que muestra información meteorológica puede actualizar la temperatura cada hora durante el día. Un widget del mercado de valores podría actualizar su contenido con frecuencia durante las horas del mercado abierto, pero no durante el fin de semana. Al planificar estos tiempos con anticipación, WidgetKit actualiza automáticamente el widget cuando llega el momento adecuado.

Cuando define un widget, se implementa un [TimelineProvider] personalizado. WidgetKit obtiene una línea de tiempo y la usa para rastrear cuándo actualizar el widget. Una línea de tiempo es una array de objetos [TimelineEntry]. Cada entrada en la línea de tiempo tiene una fecha y hora, e información adicional que el widget necesita para mostrar su vista. Además de las entradas de la línea de tiempo, la línea de tiempo especifica una política de actualización que le dice a WidgetKit cuándo solicitar una nueva línea de tiempo.

El siguiente es un ejemplo de un widget de juego que muestra el nivel de salud de un personaje. Cuando el nivel de salud es inferior al 100 por ciento, el personaje se recupera a un ritmo del 25 por ciento por hora. Por ejemplo, cuando el nivel de salud del personaje es del 25 por ciento, se necesitan 3 horas para recuperarse por completo al 100 por ciento. El diagrama muestra cómo WidgetKit solicita la línea de tiempo del proveedor, representando el widget en cada momento especificado en las entradas de la línea de tiempo.

"WidgetKit. Timeprovider: generar una línea de tiempo para eventos predecibles"

 

Informar a WidgetKit cuando cambie una línea de tiempo

La aplicación puede decirle a WidgetKit que solicite una nueva línea de tiempo cuando algo afecte la línea de tiempo actual de un widget.

En el ejemplo del widget del juego anterior, si la aplicación recibe una push notification que indica que un compañero de equipo le ha dado al personaje una poción curativa, la aplicación puede decirle a WidgetKit que vuelva a cargar la línea de tiempo y actualice el contenido del widget.

Para recargar un tipo específico de widget, la aplicación debe utilizar WidgetCenter, como se muestra a continuación:

WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.character-detail")

 

El parámetro kind contiene la misma cadena que el valor utilizado para crear la configuración de widget.

Si los widgets tienen propiedades configurables por el usuario, evitar recargas innecesarias utilizando WidgetCenter para verificar que existe un widget con la configuración adecuada.

Por ejemplo, cuando el juego recibe una notificación push sobre un personaje que recibe una poción curativa, verifica que un widget muestra ese personaje antes de volver a cargar la línea de tiempo.

En el siguiente código, la aplicación llama a [getCurrentConfigurations(_ :)] para recuperar la lista de widgets configurados por el usuario. Luego, recorre los objetos [WidgetInfo] resultantes para encontrar uno con una intención configurada con el personaje que recibió la poción curativa. Si encuentra uno, la aplicación llama a [reloadTimelines(ofKind :)] para el tipo de ese widget.

WidgetCenter.shared.getCurrentConfigurations { result in
    guard case .success(let widgets) = result else { return }

    // Iterate over the WidgetInfo elements to find one that matches
    // the character from the push notification.
    if let widget = widgets.first(
        where: { widget in
            let intent = widget.configuration as? SelectCharacterIntent
            return intent?.character == characterThatReceivedHealingPotion
        }
    ) {
        WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
    }
}


Si la aplicación usa WidgetBundle para admitir múltiples widgets, se puede usar WidgetCenter para recargar las líneas de tiempo de todos los widgets.

Por ejemplo, si los widgets requieren que el usuario inicie sesión en una cuenta pero se desconectó, puede volver a cargar todos los widgets llamando a:

WidgetCenter.shared.reloadAllTimelines()

 

 

Mostrar fechas dinámicas

Debido a que la extensión de un widget no siempre se está ejecutando, no puede actualizar directamente el contenido del widget. En cambio, WidgetKit muestra la vista del widget y muestra el resultado. Sin embargo, algunas vistas de SwiftUI permiten mostrar contenido que continúa actualizándose mientras el widget está visible.

Con una vista de tipo [Text] en el widget, se puede mostrar fechas y horas que se mantienen actualizadas en pantalla. Los siguientes ejemplos muestran las combinaciones disponibles.

Para mostrar un tiempo relativo que se actualiza automáticamente:

let components = DateComponents(minute: 11, second: 14)
let futureDate = Calendar.current.date(byAdding: components, to: Date())!

Text(futureDate, style: .relative)
// Displays:
// 11 min, 14 sec

Text(futureDate, style: .offset)
// Displays:
// -11 minutes

 

 

Hacer un widget configurable

Para que los usuarios puedan acceder fácilmente a la información más relevante, los widgets pueden proporcionar propiedades personalizables.

Por ejemplo, los usuarios pueden seleccionar una acción específica para un widget de cotización de acciones o ingresar un número de seguimiento para un widget de entrega de paquetes.

Los widgets definen propiedades personalizables mediante el uso de custom intent definitions, el mismo mecanismo que utilizan las sugerencias de Siri y los accesos directos de Siri para personalizar esas interacciones.

Para añadir propiedades configurables a un widget:

  1. Añadir un custom intent definition que defina las propiedades configurables al proyecto de Xcode.
  2. Utilizar un [IntentTimelineProvider] en el widget para incorporar las opciones del usuario en las entradas de la línea de tiempo.
  3. Si las propiedades se basan en datos dinámicos, implementar una extensión Intents.

Si la aplicación ya es compatible con las 'Sugerencias de Siri' o los 'Atajos de Siri', y existe una custom intent, probablemente la mayor parte del trabajo esté realizada. De lo contrario, considerar aprovechar el trabajo a realizar para el widget para añadir compatibilidad con las 'Sugerencias de Siri' o los 'Accesos directos de Siri'. Para obtener más información sobre cómo aprovechar al máximo Intents, puedes consultar SiriKit.

 

 

Acceder a la información de ubicación en widgets

Los widgets pueden presentar información relevante y útil teniendo en cuenta la ubicación de un usuario. Debido a que la extensión del widget no se ejecuta continuamente, el uso de los Location Services en un widget requiere que realice algunos pasos adicionales.

Para utilizar los servicios de ubicación en un widget:

  • Añadir la clave NSWidgetWantsLocation al archivo Info.plist de la extensión de widget.
  • Añadir el texto del propósito relevante al archivo Info.plist de la aplicación que contiene el widget.

Antes de que un widget pueda recibir información de ubicación, la aplicación debe solicitar la autorización del usuario.

Cuando el usuario añade un widget que utiliza la ubicación, el sistema pregunta si desea extender la autorización de ubicación de la aplicación al widget. Se deberá utilizar AuthorizedForWidgetUpdates para determinar si el widget es elegible para recibir actualizaciones de ubicación.

Una vez que el usuario activa la ubicación, si el widget no ha estado visible durante un período de tiempo, el sistema ya no lo considera en uso y deja de entregar actualizaciones de ubicación. Cuando WidgetKit vuelve a cargar la vista del widget, si authorizedForWidgetUpdates es verdadero pero el widget no recibe actualizaciones de ubicación, la ubicación del usuario no está disponible actualmente. Tener en cuenta que esto es diferente de cuando allowedForWidgetUpdates es falso, lo que indica que el usuario no ha aprobado el widget para recibir actualizaciones de ubicación.

 

 

Conclusión

Los desarrolladores disponemos de un nuevo framework WidgetKit para mostrar al usuario una extensión de la aplicación en la pantalla de Inicio en iOS o en el Centro de notificaciones en macOS.

Si bien las posibilidades para crear un widget pueden ser muy amplias, será necesario analizar qué información mostrar dependiendo de los tamaños soportados y cómo se va a actualizar esa información. Algunos widgets tendrán sentido que sean configurables por parte del usuario, pero otros pueden ser estáticos y ofrecer la misma utilidad al usuario.

La implementación durante el desarrollo de la actualización de datos es la parte que se llevará el mayor grueso de las horas de trabajo; para esto será necesario analizar y justificar la frecuencia de actualización de esos datos antes de su codificación.

Es muy importante recordar que las vistas de un widget se desarrollan con SwiftUI y el widget solo estará disponible para iOS 14, macOS 11.0 y Mac Catalyst 14.0.

 

¿Qué te ha parecido nuestro post sobre el funcionamiento de los nuevos Widgets? Esperamos que te haya resultado muy enriquecedor y no perdáis de vista nuestro blog para próximos posts.

¡Déjanos tu opinión en los comentarios!

 

Posts relacionados

Novedades de SwiftUI para iOS 15

Resumen de la WWDC21: lo que todo developer debe...

Comentaris
¿Qué opinas? Escríbenos. Nos encantará leerte :)