Crear los widgets con WidgetKit

1
1353
    1. Introducción
    2. Entorno
    3. Creamos el widget
    4. Timeline Provider
    5. Widget View
    6. Conclusiones
    7. Referencias

Introducción

En este tutorial vamos a aprender cómo crear los widgets con WidgetKit en iOS 14. La nueva version de iOS nos ha traído una pantalla de Home (Springboard) rediseñada, con la inclusión de widgets como una gran novedad. Los widgets no son solo una vista bonita. También ayudan a proporcionar información útil al usuario.
De alguna manera, son una forma más discreta de notificación que proporciona la información más reciente de las aplicaciones. Además, hay una nueva función Smart Stack en iOS 14 que agrupa un conjunto de widgets que se puede deslizar. Las pilas inteligentes tienden a proporcionar el widget relevante en la parte superior mediante el uso de inteligencia en el dispositivo que tiene en cuenta la hora del día, la ubicación etc.
Los widgets solo se puede construir con SwiftUI.

Es importante tener en cuenta que los widgets no son mini-aplicaciones.
Además de proporcionar un Link (un enlace universal y solo para los widgets de tamaño medio y grande) que permite establecer una URL para navegar a una parte particular de su aplicación, puedes vincular todo el widget con un enlace widgetURL. No se pueden agregar animaciones u otras interacciones en widgets (como campos, botones etc.).

Entorno

Solo se pueden crear los proyectos que utilizan WidgetKit a partir de Xcode 12.

Creamos el widget

Tenemos una aplicación que nos ayuda a gestionar las tareas pendientes. Es una aplicación muy simple que tiene la interfaz gráfica así:

Añadimos nuevo target de Widget Extension al proyecto de Xcode 12 y aseguramos que se elija el ciclo de vida de la aplicación SwiftUI.
Para crear nuestro primer widget, vete a File → New → Target y selecciona la plantilla «Widget Extension».

Al añadir el target «Widget Extension» puedes ver mucho código que no entiendas. Vamos a repasarlo.

Para crear los widgets se utiliza el framework WidgetKit. El código de la estructura TaskWidget es el punto de partida para nuestro widget:

@main
struct TaskWidget: Widget {
    private let kind: String = "TaskWidget"

    public var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            TaskWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Task Widget")
        .description("The widget that show pending tasks for today.")
        .supportedFamilies([.systemLarge, .systemMedium, .systemSmall])
    }
}
  • kind es un identificador utilizado para distinguir el widget de otros en el WidgetCenter.
  • el contenido del widget se establece dentro de TaskWidgetEntryView que veremos en breve.
  • el provider es una estructura de tipo TimelineProvider que es el motor central del widget. Es responsable de alimentar el widget con datos y establecer intervalos para actualizar los datos.
  • los modificadores de vista configurationDisplayName y description muestran información respectiva en la galería de widgets, un lugar que muestra todos los widgets de tu dispositivo.

Hay otro nuevo modificador supportedFamilies que contiene los diferentes tamaños de widgets que queremos para nuestra aplicación. Por ejemplo, .supportedFamilies ([. .systemLarge]) solo permite widgets de gran tamaño.

Timeline Provider

Como su nombre indica, la estructura del provider busca proporcionar datos para el contenido del widget. Se ajusta a un protocolo TimelineProvider que requiere la implementación de tres métodos (getSnapshotgetTimeline y placeholder):

struct Provider: IntentTimelineProvider {
    
    public func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> Void) {
        let entry = SimpleEntry(date: Date(), tasks: [TaskModel(id: UUID(), title: "task", note: "SimpleNote", completed: false)])
        completion(entry)
    }

    public func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
        var entries: [SimpleEntry] = []
        let tasksUncompleted = CoreDataManager.sharedInstance.fetchAllTodayUnCompletedTasks().map(TaskModel.init)
        // Generate a timeline consisting of entries of tasks that should will be completed today.
        for i in 0 ..< tasksUncompleted.count {
            let entryDate = DateUtils.getDateFromString(for: tasksUncompleted[i].dueDate)?.addingTimeInterval(-6200)
            let entry = SimpleEntry(date: entryDate ?? Date(), tasks: tasksUncompleted)
            entries.append(entry)
        }
        if entries.isEmpty {
            entries.append(SimpleEntry(date: Date(), tasks: []))
        }
        let timeline = Timeline(entries: entries, policy:.atEnd)
        completion(timeline)
    }
    
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), tasks: [TaskModel(id: UUID(), title: "Task", note: "Note", completed: false)])
    }
}

La función de getSnapshot se usa para presentar inmediatamente una vista de widget, mientras que el provider intenta cargar los datos. Es importante que configures el método de getSnapshot solo con datos ficticios, ya que esta vista también se mostrará en la Galería de widgets. La función getTimeline, por otro lado, se utiliza para crear una o más entradas. Podemos establecer el intervalo de tiempo después del cual se debe actualizar TimelineEntry. El código anterior crea las entradas que contiene las tareas de hoy. Se usarán para actualizar el contenido del widget.

La instancia de la clase Timeline también contiene una TimelineReloadPolicy. El sistema usa esta política para determinar cuándo invocar nuevamente la función de getTimeline, para cargar el siguiente conjunto de entradas. En el ejemplo anterior, la política se establece como atEnd (al final), lo que significa que después de que se muestre el SimpleEntry en el widget, el sistema activará la función getTimeline para la siguiente entrada.
Además de atEnd, también podemos usar afterDate. Se utiliza para establecer una fecha específica en la que queremos obtener la siguiente entrada. Ten en cuenta que el sistema puede no activar la función de getTimeline en la fecha exacta. Además, podemos establecer una política de never para garantizar que el método getTimeline no se active nuevamente. Además, si deseas activar una recarga de entradas de widgets, puede usar la API de WidgetCenter. Nos permite recargar todas las entradas de timeline de nuevo.

struct SimpleEntry: TimelineEntry {
    public let date: Date
    public let tasks: [TaskModel]
    public let configuration: ConfigurationIntent
}

Tienes que definir la estructura de la entrada de timeline. Normalmente contiene los datos necesarios para mostrar en el widget.

El método placeholder se usa para definir la interfaz gráfica de widget mientras se carga. Hace falta pasar una simple entrada y lo demás se hace SwiftUI solo.

Widget View

Ahora que tenemos claro cómo funciona el provider, veamos la vista SwiftUI que mostrará como el widget en la pantalla.

struct TaskWidgetEntryView : View {
    var entry: Provider.Entry
    
    @Environment(\.widgetFamily) var family
    
    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall:
            if entry.tasks.count > 0 {
                VStack(alignment: .leading) {
                    ForEach(entry.tasks, id: \.self) { task in
                        TaskView(task: task)
                   }
                }.padding(5)
            } else {
                Text("No tasks")
            }
        default:
            if entry.tasks.count > 0 {
                VStack(alignment: .leading) {
                    ForEach(entry.tasks, id: \.self) { task in
                        TaskView(task: task)
                   }
                }.padding(5)
            } else {
                Text("No tasks")
            }
        }
        
    }
}

WidgetKit llena el widget con el conjunto de datos de la entrada del provider. La propiedad entry es la fuente de datos para la vista de nuestro widget.
TaskView es una vista que contiene la principal parte de nuestra interfaz gráfica. Utilizamos la imagen y el texto. Es una interfaz super simple.

struct TaskView: View {
    let task: TaskModel
    
    var body: some View {
        HStack {
            Image(systemName: "square").foregroundColor(.red)
            Text(task.title )
            .font(.system(size: 15))
        }
        Text(task.dueDate)
            .font(.system(size: 10))
        Text(task.note)
            .font(.system(size: 10))
    }
}

Si añadimos nuestro widget a la pantalla Home veremos la siguiente imagen:
El widget solo te muestra la información, no podemos interactuar con las vistas del widget. Es una manera de informar el usuario sobre el actual estado de aplicación. Si el usuario pulsa el widget se le abre la aplicación. Hay solo una manera de añadir diferentes enlaces al widget. Con la ayuda de universal links se puede añadir los links al widget utilizando Link API. Son como web links que te dirigen a las diferentes partes de aplicación.

Conclusiones

Se puede crear muy fácilmente los widget usando la API de WidgetKit. Es una nueva manera de mostrar al usuario el estado de aplicación o solamente una parte importante de aplicación. No se puede interactuar con el widget. Es solo una fachada o escaparate de nuestra aplicación. Aún así puede facilitar al usuario el uso diario de nuestra aplicación mostrando la información importante en este momento para él.

Si quieres descargar el proyecto con el ejemplo el repositorio con el proyecto está aquí.

1 COMENTARIO

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad