Property Wrapper EnvironmentObject en SwiftUI
Property Wrapper EnvironmentObject en SwiftUI

@EnvirontmentObject en SWIFTUI en Español

EnvironmentObject es otro Property Wrapper en SwiftUI que usamos para proporcionar desde alguna vista padre una clase que podrá ser usada en toda su jerarquía de vistas. Es muy sencillo, es decir, metemos en el environment una clase instanciada y esta clase podrá ser usada en todas sus subvistas.

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
Aprende SwiftUI desde cero Property Wrapper EnvironmentObject
Aprende SwiftUI desde cero Property Wrapper EnvironmentObject

EnvironmentObject es otro Property Wrapper que usamos para proporcionar desde alguna vista padre una clase que podrá ser usada en toda su jerarquía de vistas. Es muy sencillo, es decir, metemos en el environment una clase instanciada y esta clase podrá ser usada en todas sus subvistas, como si fuera magia.

En uno de los post anteriores explicamos cuándo usar ObservedObject y StateObject. En ese caso, dijimos de usar StateObject la primera vez que usábamos un ViewModel y qué si ese tipo de ViewModel lo necesitábamos en otra de sus subvistas, entonces usar ObservedObject (si quieres saber más te aconsejo que lo leas). Esto te lo comento ya que lo vamos a usar en nuestro ejemplo.

EnvironmentObject en código

Vamos a crear una clase llamada ViewModel que conforme el protocolo ObservableObject. Y también vamos a crear 3 vistas llamadas View1, View2 y View3 para que sea más fácil de entender.
View1 tendrá en su interior View2, y View2 tendrá a View3, es decir:

En View1 mostraremos un text que mostrará el valor de una propiedad llamada counter que estará en nuestro ViewModel. View1 le pasará el ViewModel a View2 y View2 le pasará el ViewModel a View3 para que al pulsar un botón actualice la propiedad counter, y este cambio se propague y View1 muestre el valor actualizado.

Primero de todo lo que vamos hacer es crear nuestro ViewModel con una propiedad llamada counter:

class ViewModel: ObservableObject {
    @Published var counter: Int = 0
}
Confomar ObservableObject en SwiftUI

El siguiente paso es renombrar ContentView a View1 y añadir el siguiente código:

struct View1: View {
    
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("Counter: \(viewModel.counter)")
                .bold()
                .font(.largeTitle)
            Text("View 1")
                .padding()
        }
    }
}
Código SwiftUI con Property Wrapper StateObject

Ahora creamos View2:

struct View2: View {
        
    var body: some View {
        VStack {
            Text("View 2")
                .padding()
        }
    }
}
Código SwiftUI de View2

y por último la View3:

struct View3: View {
        
    var body: some View {
        VStack {
            Text("View 3")
                .padding()
            Button("Incrementar") {
                // TODO
            }
        }
    }
}
Código SwiftUI de View3

Falta conectar estas 3 vistas. Ahora vamos a meter View3 en View2, y View2 en View1. Y también le vamos a pasar el ViewModel de View1 a View2 y de View2 a View3. Así podremos modificar la propiedad counter de nuestro ViewModel en View3.

Por lo tanto View3 quedaría de la siguiente manera:

struct View3: View {
    
    @ObservedObject var viewModel
    
    var body: some View {
        VStack {
            Text("View 3")
                .padding()
            Button("Incrementar") {
                viewModel.counter += 1
            }
        }
    }
}
Añadimos Property Wrapper ObservedObject en View3

View2 quedaría:

struct View2: View {
    
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("View 2")
                .padding()
            View3(viewModel: viewModel)
        }
    }
}
Añadimos Property Wrapper ObservedObject en View2

y View1:

struct View1: View {
    
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("Counter: \(viewModel.counter)")
                .bold()
                .font(.largeTitle)
            Text("View 1")
                .padding()
            View2(viewModel: viewModel)
        }
    }
}
Añadimos Property Wrapper StateObject en View1

Ya está todo conectado. De esta manera si probamos nuestro código vemos como todo funciona perfectamente, al pulsar el botón en View3, se actualizar el valor en View1 y mostramos el valor correcto.

PERO si te fijas hemos tenido que pasar el ViewModel en toda la jerarquía de vistas, aunque en View2 no lo hemos usado, se lo hemos tenido que pasar para poderlo usar en View3. Imagínate que en lugar de tener 3 simples vistas tienes muchas más.

Es aquí donde entra en juego EnvironmentObject. Lo único que tenemos que hacer es usar en nuestra vista View1 el modificador .environmentObject y pasarle nuestro ViewModel instanciado:

struct View1: View {
    
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("Counter: \(viewModel.counter)")
                .bold()
                .font(.largeTitle)
            Text("View 1")
                .padding()
            View2()
        }
        .environmentObject(viewModel)
    }
}
Código SwiftUI usando el modificador environtmentObject

De esta manera, simplificamos nuestro código en View2, ya que no nos hace falta pasarle el ViewModel:

struct View2: View {
        
    var body: some View {
        VStack {
            Text("View 2")
                .padding()
            View3()
        }
    }
}
Eliminamos la propiedad ObservedObject en View2

y en View3 solo tenemos que cambiar el ObservedObject por EnvironmentObject.

struct View3: View {
    
    @EnvironmentObject var viewModel: ViewModel
    
    var body: some View {
        VStack {
            Text("View 3")
                .padding()
            Button("Incrementar") {
                viewModel.counter += 1
            }
        }
    }
}
Código SwiftUI con Property Wrapper EnvironmentObject

En View3 coge la instancia que le hemos metido en View1 y la puede usar sin problemas. Aquí hay que ir con especial atención, ya que si no añadimos la clase instanciada en el modificador .environmentObject, nuestra app crashería al pulsar el botón de "Incrementar".