Formamos View en SwiftUI, a partir de las condiciones

A veces necesitamos crear una Vista SwiftUI, dadas algunas condiciones. Por ejemplo, en el código anterior, definimos una HomeView , que puede contener una ProfileView , si LogInManager ha logInUser . Estamos tratando de implementar esto usando la declaración estándar if:

struct HomeView: View {
    @ObservedObject var loginManager: LoginManager

    var body: some View {
        VStack {
            if let user = loginManager.loggedInUser {
                ProfileView(user: user)
            }

            ...
        }
    }
}

Desafortunadamente, este código arrojará un error al compilar:
El cierre que contiene la declaración de flujo de control no se puede usar con el generador de funciones ViewBuilder.

Como los cierres de funciones no se usan aquí, sino los creadores de funciones , no podemos ponerles código arbitrario para formar un HStack o VStack. Entonces, ¿cómo salimos de esta situación?

Una forma es pasar el procesamiento de tales opciones directamente a la vista que estamos creando. Por ejemplo, podemos pasar a nuestro ProfileView no un valor de Usuario específico, pero hacerlo opcional:

struct ProfileView: View {
    var user: User?

    var body: some View {
        guard let user = user else {
            // We have to use 'AnyView' to perform type erasure here,
            // in order to give our 'body' a single return type:
            return AnyView(EmptyView())
        }

        return AnyView(VStack {
            Text(user.name)
            ...
        })
    }
}

Este código funciona, pero no es particularmente hermoso. No tiene sentido crear un ProfileView para el usuario nulo. Tomemos un enfoque diferente: use el mapa para nuestro Usuario opcional para convertirlo a ProfileView:

struct HomeView: View {
    @ObservedObject var loginManager: LoginManager

    var body: some View {
        VStack {
            loginManager.loggedInUser.map { user in
                ProfileView(user: user)
            }
            ...
        }
    }
}

Esto ya es mucho más bonito: no necesitamos dar manualmente EmptyView cuando el usuario no tiene ningún valor. De nuevo podemos pasar un valor específico a ProfileView, no opcional. ¿Es posible hacerlo aún mejor?

La buena noticia sobre @ViewBuilder es que no se trata de algún tipo de implementación privada en SwiftUI, sino de un atributo accesible con el que podemos anotar nuestras propias funciones y cierres.

Con este atributo, podemos compilar una vista Desenvolver, que toma un valor opcional como parámetro y un cierre etiquetado @ViewBuilder para convertir un valor no nulo en Vista:

struct Unwrap<Value, Content: View>: View {
    private let value: Value?
    private let contentProvider: (Value) -> Content

    init(_ value: Value?,
         @ViewBuilder content: @escaping (Value) -> Content) {
        self.value = value
        self.contentProvider = content
    }

    var body: some View {
        value.map(contentProvider)
    }
}

Con este diseño, ahora podemos rediseñar completamente la estructura completa de HomeView:

struct HomeView: View {
    @ObservedObject var loginManager: LoginManager

    var body: some View {
        VStack {
            Unwrap(loginManager.loggedInUser) { user in
                HStack {
                    Text("Logged in as:")
                    ProfileView(user: user)
                }
            }
            ...
        }
    }
} 

All Articles