wandres.dev
SWIFTUI AVANZADO · @Observable y animaciones

Environment: inyección sin cables

Comparte un modelo con todo un árbol de vistas sin pasarlo mano a mano. @Environment inyecta dependencias de forma limpia y desacoplada.

⏱ 12 min

Pasar un modelo de vista en vista mediante parámetros funciona… hasta que tienes cinco niveles de profundidad y cada vista intermedia lo reenvía sin usarlo. El environment resuelve esto: inyectas el modelo una vez y cualquier vista descendiente lo pide directamente.

🎯 Al terminar esta lección sabrás
  • El problema del “prop drilling”.
  • Inyectar un modelo con .environment(...).
  • Leerlo con @Environment(Modelo.self).
  • Los valores del environment del sistema.

El problema: pasar a mano

Sin environment, un modelo que necesita una vista profunda tiene que atravesar todas las intermedias:

// tedioso: B y C no usan el modelo, solo lo reenvían
VistaA(modelo: modelo)
 VistaB(modelo: modelo)
 VistaC(modelo: modelo)
 VistaD(modelo: modelo)   // la única que lo usa

La solución: inyectar en el environment

Inyectas el modelo en un punto alto del árbol y desaparece de las firmas intermedias:

@main
struct MiApp: App {
    @State private var sesion = SesionModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(sesion)      // inyectado para TODO el árbol
        }
    }
}

Y cualquier vista descendiente, a cualquier profundidad, lo pide:

struct PerfilView: View {
    @Environment(SesionModel.self) private var sesion

    var body: some View {
        Text("Hola, \(sesion.usuario)")
    }
}

Ninguna vista intermedia necesita saber que SesionModel existe. Limpio y desacoplado.

Cuándo environment y cuándo parámetros

Regla práctica: usa parámetros para datos que una vista concreta necesita (el Tarea que muestra una fila), y environment para dependencias transversales que muchas vistas comparten (la sesión, el tema, un router, el modelo de datos global). El environment no es para todo: abusar de él oculta las dependencias y dificulta ver qué necesita cada vista. Para lo compartido de verdad, es perfecto; para lo local, un parámetro explícito es más claro.

Bindings desde el environment

¿Necesitas editar una propiedad del modelo del environment? Reenvuélvelo con @Bindable dentro del body:

struct AjustesView: View {
    @Environment(AjustesModel.self) private var ajustes

    var body: some View {
        @Bindable var ajustes = ajustes      // habilita los $bindings
        Toggle("Notificaciones", isOn: $ajustes.notificaciones)
    }
}

Los valores del sistema

El environment ya trae decenas de valores del sistema que puedes leer:

@Environment(\.colorScheme) private var esquema     // .light o .dark
@Environment(\.dismiss) private var dismiss          // cerrar una hoja
@Environment(\.locale) private var locale            // idioma/región

// ejemplo: un botón que cierra la pantalla actual
Button("Cerrar") { dismiss() }

\.dismiss es especialmente útil: cualquier vista presentada como hoja puede cerrarse a sí misma sin que el padre le pase nada.

💡
Environment values propios

Puedes crear tus propios valores de environment (con @Entry en iOS moderno) para inyectar configuración a medida por todo el árbol. Empieza usando los del sistema y los modelos @Observable; los valores personalizados llegan cuando los necesites.

⚔️ Inyecta sin cables
  1. Crea un modelo @Observable (p. ej. SesionModel con un usuario).
  2. Inyéctalo en el WindowGroup con .environment(...).
  3. Léelo en una vista profunda con @Environment(SesionModel.self).
  4. En otra vista, usa @Bindable var local para editar una propiedad con un Toggle.
  5. Añade un botón “Cerrar” que use @Environment(\.dismiss).