Navegación entre vistas usando @EnvironmentObject en SwiftUI

La traducción del artículo fue preparada en la víspera del lanzamiento del curso avanzado "Desarrollador iOS" .




Hola y bienvenidos a nuestro tutorial! En esta serie, hablamos sobre cómo navegar entre vistas en SwiftUI (¡sin usar una vista de navegación!). Aunque esta idea puede parecer trivial, pero después de comprenderla un poco más, podemos aprender mucho sobre los conceptos de flujo de datos utilizados en SwiftUI.

En la parte anterior, aprendimos cómo implementar esto usando @ObservableObject. En esta parte, veremos cómo hacer lo mismo, pero de manera más eficiente usando @EnvironmentObject. También vamos a agregar una pequeña animación de transición.

Esto es lo que vamos a lograr:


Que tenemos


Entonces, acabamos de descubrir cómo navegar entre diferentes vistas usando ObservableObject. En pocas palabras, creamos un ViewRouter y lo asociamos con la Vista Madre y nuestra Vista de Contenido. Luego, simplemente manipulamos la propiedad CurrentPage ViewRouter haciendo clic en los botones de Vista de contenido. Después de eso, MotherView se actualiza para mostrar la vista de contenido correspondiente.

Pero hay una segunda forma más eficiente de lograr esta funcionalidad: @EnvironmentObject¡ usar !

Sugerencia: puede descargar los últimos desarrollos aquí (esta es la carpeta "NavigateInSwiftUIComplete") : GitHub

¿Por qué el uso ObservableObjectno es la mejor solución?


Probablemente se esté preguntando: ¿por qué deberíamos implementar esto de otra manera, cuando ya tenemos una solución que funcione? Bueno, si observa la lógica de la jerarquía de nuestra aplicación, se le hará evidente. Nuestra MotherView es la vista raíz que inicializa la instancia de ViewRouter. En MotherView, también inicializamos ContentViewA y ContentViewB, pasándoles la instancia de ViewRouter como BindableObject.

Como puede ver, debemos seguir una jerarquía estricta que pasa un ObservableObject inicializado en sentido descendente a todas las subvistas. Ahora esto no es tan importante, pero imagine una aplicación más compleja con muchas vistas. Siempre necesitamos realizar un seguimiento de la transmisión de la vista raíz inicializable Observable a todas las subvistas y todas las subvistas de subvistas, etc., lo que en última instancia puede convertirse en una tarea bastante tediosa.



Para resumir: usar clean ObservableObjectpuede ser problemático cuando se trata de jerarquías de aplicaciones más complejas.

En cambio, podríamos inicializar ViewRouter cuando se inicie la aplicación para que todas las vistas se puedan conectar directamente a esta instancia, o, más bien, verla, independientemente de la jerarquía de la aplicación. En este caso, la instancia de ViewRouter se verá como una nube que vuela sobre el código de nuestra aplicación, a la que todas las vistas acceden automáticamente sin preocuparse por la cadena de inicialización correcta en la jerarquía de vistas.
Este es solo el trabajo EnvironmentObject!

¿Qué es EnvironmentObject?


EnvironmentObjectEs un modelo de datos que, después de la inicialización, puede intercambiar datos con todas las representaciones de su aplicación. ¡Lo que es especialmente bueno es lo que se EnvironmentObjectcrea al proporcionar ObservableObject, para que podamos usar el nuestro ViewRouterpara crear EnvironmentObject!

Tan pronto como declaramos nuestro ViewRoutercomo EnvironmentObject, todas las vistas pueden ser conectados al mismo, así como a las normales ObservableObject, pero sin la necesidad de una cadena de inicialización abajo de la jerarquía de la aplicación!

Como ya se mencionó, EnvironmentObjectya debe inicializarse la primera vez que se accede. Como la nuestra MotherView, como vista raíz, analizará la propiedad CurrentPage ViewRouter, debemos inicializarla EnvironmentObjectcuando se inicie la aplicación. Entonces podemos cambiar automáticamente la página actualEnvironmentObjectde ContentView, que luego requiere MotherViewuna nueva representación.



Implementación ViewRoutercomoEnvironmentObject


Entonces, ¡actualice el código de nuestra aplicación!

Primero, cambie el contenedor de la propiedad viewRouterdentro MotherViewde @ObservableObjecta @EnvironmentObject.

import SwiftUI

struct MotherView : View {
@EnvironmentObject var viewRouter: ViewRouter
var body: some View {
//...
    }
}

La propiedad viewRouterahora está buscando ViewRouter-EnvironmentObject. Por lo tanto, debemos proporcionar a nuestra estructura MotherView_Previewsla instancia adecuada:

#if DEBUG
struct MotherView_Previews : PreviewProvider {
    static var previews: some View {
        MotherView().environmentObject(ViewRouter())
    }
}
#endif

Como se mencionó anteriormente, al iniciar nuestra aplicación, se le debe proporcionar inmediatamente una instancia ViewRouterde calidad EnvironmentObject, ya que MotherViewahora se conoce como la representación raíz EnvironmentObject. Por lo tanto, actualice la función de escena dentro del archivo de la SceneDelegage.swiftsiguiente manera:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(ViewRouter()))
            self.window = window
            window.makeKeyAndVisible()
        }
    }

Genial, ahora cuando inicia la aplicación, SwiftUI crea una instancia ViewRouteren calidad EnvironmentObject, a la que ahora se pueden adjuntar todas las vistas de nuestra aplicación.

A continuación, vamos a actualizar la nuestra ContentViewA. Cambie su viewRouterpropiedad a EnvironmentObjecty también actualice la estructura ContentViewA_Previews.

import SwiftUI

struct ContentViewA : View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View {
       //...
    }
}
#if DEBUG
struct ContentViewA_Previews : PreviewProvider {
    static var previews: some View {
        ContentViewA().environmentObject(ViewRouter())
    }
}
#endif

Sugerencia: De nuevo, la estructura ContentViewsA_Previewstiene su propia instancia ViewRouter, ¡pero ContentViewAestá asociada con la instancia creada cuando se lanzó la aplicación!

Repitamos esto para ContentViewB:

import SwiftUI

struct ContentViewB : View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View {
        //...
    }
}
#if DEBUG
struct ContentViewB_Previews : PreviewProvider {
    static var previews: some View {
        ContentViewB().environmentObject(ViewRouter())
    }
}
#endif

Dado que viewRouternuestras propiedades ContentViewahora están directamente relacionadas con / observar la instancia inicial ViewRoutercomo EnvironmentObject, ya no necesitamos inicializarlas en la nuestra MotherView. Entonces, vamos a actualizar nuestro MotherView:

struct MotherView : View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View { 
        VStack {
            if viewRouter.currentPage == "page1" {
                ContentViewA()
            } else if viewRouter.currentPage == "page2" {
                ContentViewB()
            }
        }
    }
}

Esto es simplemente genial: ya no necesitamos inicializar ViewRouterdentro de la nuestra MotherViewy pasar su instancia a ContentView, que puede ser muy eficiente, especialmente para jerarquías más complejas.

Genial, ejecutemos nuestra aplicación y veamos cómo funciona.


¡Genial, todavía podemos movernos entre nuestros puntos de vista!

Agregar animaciones de transición


Como beneficio adicional, veamos cómo agregar una animación de transición al cambiar de “página1” a “página2”.

En SwiftUI, esto es bastante simple.
Mire el willChangemétodo que llamamos el archivo ViewRouter.swiftcuando se CurrentPageactualiza. Como ya sabe, esto provoca un enlace MotherViewpara volver a mostrar su cuerpo, en última instancia, muestra otro ContentView, lo que significa pasar a otro ContentView. Podemos agregar animación simplemente envolviendo el método willChangeen una función withAnimation:

var currentPage: String = "page1" {
        didSet {
            withAnimation() {
                willChange.send(self)
            }
        }
    }

Ahora podemos agregar una animación de transición al mostrar otra Content View.
"WithAnimation (_: _ :): devuelve el resultado de volver a calcular el cuerpo de la vista con la animación proporcionada"
Apple
Queremos proporcionar a nuestra aplicación una transición emergente cuando navegue de ContentViewAa ContentViewB. Para hacer esto, vaya al archivo MotherView.swifty agregue el modificador de transición cuando se le llame ContentViewB. Puede elegir uno de varios tipos de transición predefinidos o incluso crear uno propio (pero este es un tema para otro artículo). Para agregar una transición emergente, seleccionamos un tipo .scale.

var body: some View {
        VStack {
            if viewRouter.currentPage == "page1" {
                ContentViewA()
            } else if viewRouter.currentPage == "page2" {
                ContentViewB()
                    .transition(.scale)
            }
        }
    }

Para ver cómo funciona, ejecute su aplicación en un simulador normal:


¡Genial, agregamos una agradable animación de transición a nuestra aplicación en solo unas pocas líneas de código!

¡Puedes descargar todo el código fuente aquí !

Conclusión


¡Eso es todo! Aprendimos por qué es mejor usarlo EnvironmentObjectpara navegar entre vistas en SwiftUI y cómo implementarlo. También aprendimos cómo agregar animaciones de transición a la navegación. Si quieres saber más, síguenos en Instagram y suscríbete a nuestro boletín para no perderte actualizaciones, tutoriales y consejos sobre SwiftUI y mucho más.



Lección gratuita: "Aceleración de aplicaciones iOS con instrumentos"



All Articles