À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 {
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)
}
}
...
}
}
}