Enable javascript in your browser for better experience. Need to know to enable it? Go here.
10 pasos para reducir el impacto del Síndrome del Impostor

Guía para entender los Property Wrappers en SwiftUI

Una de las principales preguntas que nos surge al construir una app con SwiftUI, el nuevo framework de UI de Apple, es cómo manejar un estado entre distintos flujos y vistas. Para ello Apple provee nuevas maneras de manejar los flujos de datos. Siendo este tema uno de los más abstractos al momento de abordar una app con SwiftUI.

 

Pero, ¿qué son los properties wrappers? Son esencialmente, un tipo de dato que envuelve a una variable para poder añadir más funcionalidad y lógica. Es una forma rápida de obtener la funcionalidad ya lista para su uso.

 

Para este tutorial: supongamos que estamos desarrollando un eCommerce mobile app. Para ello estamos siguiendo el patrón MVVM, Modelo Vista Vista-Modelo.

Aplicación de eCommerce, donde se muestra un automóvil negro con sus características

@State


Podemos usar este property wrapper para mantener un estado local a la vista. Por ejemplo, el estado enabled o disabled de un botón.

Botón de enabled y disabled dentro de la aplicación

En el caso de nuestra app, el botón para realizar una compra lo podemos hacer persistir de la siguiente manera en nuestra interfaz de usuario. 

 

struct BuyButton: View {
   @State var isDisabled: Bool = false
   var body: some View {
        Button("Hacer la compra", action: {})
       .disabled(isDisabled)
   }
}

 

https://gist.github.com/Thonyvb/0147039825f003ee307143e427f0ab9f

 

Un cambio en @State var isDisabled  produce que los elementos de interfaz como nuestro botón, reaccionen automáticamente si el valor cambia.

 

@Binding

 

Siguiendo con nuestro ejemplo, supongamos que tenemos una subvista que incluye un checkbox de favoritos. 

Checkbox de favoritos dentro de la aplicación

Si deseamos que la subvista, FavButton, pueda interactuar con el estado de su vista padre ProductView podemos usar @Bindings para referenciarlos de la siguiente manera:

struct ProductView: View {
   @State private var isFavorite: Bool = false

   var body: some View {
 // ... 
     FavButton(isFavorite: $isFavorite) // Pass a binding.
   }
}
struct FavButton: View {
   @Binding var isFavorite: Bool

   var body: some View {
       Button(action: {
          isFavorite.toggle()
       }, label: {
          Image(isFavorite ? "Filled_Heart" : "Heart")
       })
   }
}

https://gist.github.com/Thonyvb/69dff923fc54ed123404b16e1b7e4600

 

Es decir, nuestra subvista va a reaccionar al valor de @State private var isFavorite: Bool = false. El valor de isFavorite puede cambiar desde la sub vista FavBoton al recibir un click en el botón, y este valor ahora persiste en la vista padre ProductView.

 

Para los siguientes pasos en nuestra aplicación, necesitamos manejar información que puede provenir de otras fuentes, como por ejemplo: llamadas al backend. El requisito de nuestra aplicación es poder tener en tiempo real los últimos precios ofertados por nuestro producto. Para esto, vamos a usar eventos asincrónicos que propaguen información a nuestra interfaz de manera inmediata. Esto también conocido como Reactive Programming.

 

ObservableObject

 

Cuando creamos un ObservableObject estamos haciendo que una clase publique cambios de los datos que estamos manejando. Vamos a crear nuestro objeto Prices el cual va a mantener un listado de precios obtenidos desde una llamada al servidor. 

class Prices: ObservableObject {
   @Published var prices: [Int]
   // ... obtain prices via API
}

https://gist.github.com/Thonyvb/06dc3da1699bc8850ec11d8434e5c2f4

 

@Published

 

Uno de los property wrappers más útiles en el desarrollo de SwiftUI, @Published nos permite crear publicadores de eventos. 

 

Con este property wrapper podemos publicar los cambios del listado de precios una vez que obtengamos nuevos datos desde nuestro servidor. Es decir; cuando existan cambios en el listado de prices, todas las interfaces de usuarios suscritas van a tener la última versión de los precios.

Muestra de un cambio de precio en un artículo dentro de la aplicación

Y para poder completar el flujo de datos, nuestras vistas y/o subvistas necesitan tener una comunicación con el objeto Prices para poder obtener actualizaciones del listado de precios. Para ello, Apple nos entrega varias maneras de establecer la comunicación entre los ObservableObjects y nuestras vistas.

 

@StateObject

 

Lo usamos cuando la vista es dueña del ObservableObject, es decir que nuestra vista crea una nueva instancia.

 

En nuestro caso de uso, la vista que inicia el flujo es quien crea el ObservableObject Prices para que mantenga el estado de los últimos precios de la subasta. El syntax para realizarlo es de la siguiente manera en una vista:

 

struct MainView: View {
    @StateObject var prices = Prices()
    // …

https://gist.github.com/Thonyvb/247a1b6c8421986763dc365bb405b163

 

@ObservedObject

 

Si nosotros recibimos el ObservableObject de una vista padre, la subvista puede declarar un @ObservedObject

 

Por ejemplo, la vista principal que mantiene el ObservableObject con los precios de la subasta puede compartir este ObservableObject con su subvista que maneja el carrito de compras.

Subvista del carrito de compras dentro de la aplicación
struct CartView: View {
    @ObservedObject var prices: Prices
    // … 

https://gist.github.com/Thonyvb/dc539d304da4c022f8de017eb9bba851

 

@EnvironmentObject

 

Si necesitamos compartir nuestro ObservableObject de precio entre varias sub vistas, podemos evitar el uso repetitivo de @ObservedObject al compartirlo una sola vez desde nuestra vista principal usando @EnvironmentObject

 

Esto nos ayuda a mantener una sola fuente de verdad, ya que todas las vistas van a reaccionar a los cambios de un solo ObservableObject.

struct MainView: View {
    @StateObject var prices = Prices()

    var body: some View {
            VStack {
                PricesView()
            }.environmentObject(prices)
    }
}

 

 

struct PricesView: View {
    @EnvironmentObject var prices: Prices
    // …

https://gist.github.com/Thonyvb/cfee01d49540f79451270a466dd42716

 

Siguientes Pasos

 

El uso de los property wrappers es uno de los temas más desafiantes en SwiftUI, en especial porque requiere un análisis previo del diseño de la solución para poder usarlos de manera correcta y eficiente. Un uso inadecuado puede provocar múltiples re-renders de la interfaz de usuario que pueden impactar en los recursos del dispositivo, anti-patrones que pueden hacer el código muy difícil de mantener, o la pérdida de la reactividad de la interfaz, que implica que no aprovechamos el potencial de este framework. Una vez dominado los conceptos básicos de los property wrappers, un gran siguiente paso es explorar el framework de programación reactiva llamado Combine.  


*Illustraciones por Xavier Idrovo

 

 

Fuentes:

https://developer.apple.com/documentation/swiftui/state-and-data-flow

Aviso legal: Las declaraciones y opiniones expresadas en este artículo son las del autor/a o autores y no reflejan necesariamente las posiciones de Thoughtworks.

¿Quieres formar parte de nuestro equipo?