Formamos o View no SwiftUI, partindo das condições

Às vezes, precisamos criar uma visualização SwiftUI, dadas algumas condições. Por exemplo, no código acima, definimos um HomeView , que pode conter um ProfileView , se LogInManager tiver logInUser . Estamos tentando implementar isso usando a instrução if padrão:

struct HomeView: View {
    @ObservedObject var loginManager: LoginManager

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

            ...
        }
    }
}

Infelizmente, esse código gera um erro ao compilar:
O fechamento que contém a instrução de fluxo de controle não pode ser usado com o construtor de funções ViewBuilder.

Como os fechos de função não são usados ​​aqui, mas os construtores de funções , não podemos inserir código arbitrário neles para formar um HStack ou VStack. Então, como vamos sair dessa situação?

Uma maneira é passar o processamento desses opcionais diretamente para a visão que estamos criando. Por exemplo, podemos passar em nosso ProfileView não um valor específico de usuário, mas torná-lo 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)
            ...
        })
    }
}

Esse código funciona, mas não é particularmente bonito. Não faz sentido criar um ProfileView para o usuário nulo. Vamos adotar uma abordagem diferente: use map para nosso usuário opcional para convertê-lo em ProfileView:

struct HomeView: View {
    @ObservedObject var loginManager: LoginManager

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

Isso já é muito mais bonito: não precisamos fornecer manualmente o EmptyView quando o Usuário não tiver valor. Podemos novamente passar um valor específico para o ProfileView, não opcional. É possível fazer ainda melhor?

A boa notícia sobre o @ViewBuilder é que esse não é um tipo de implementação privada no SwiftUI, mas um atributo acessível com o qual podemos anotar nossas próprias funções e fechamentos.

Usando esse atributo, podemos compilar uma exibição Desembrulhar, que aceita um valor opcional como parâmetro e um fechamento marcado com o @ViewBuilder para converter um valor não nulo em Exibir:

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)
    }
}

Usando esse design, agora podemos redesenhar completamente toda a estrutura do 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