Nous formons View dans SwiftUI, en partant des conditions

Parfois, nous devons créer une vue SwiftUI, compte tenu de certaines conditions. Par exemple, dans le code ci-dessus, nous définissons un HomeView , qui peut contenir un ProfileView , si LogInManager a logInUser . Nous essayons de l'implémenter en utilisant l'instruction if standard:

struct HomeView: View {
    @ObservedObject var loginManager: LoginManager

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

            ...
        }
    }
}

Malheureusement, ce code générera une erreur lors de la compilation:
La fermeture contenant l'instruction de flux de contrôle ne peut pas être utilisée avec le générateur de fonctions ViewBuilder.

Comme les fermeteurs de fonctions ne sont pas utilisés ici, mais les constructeurs de fonctions , nous ne pouvons pas y mettre du code arbitraire pour former un HStack ou VStack. Alors, comment sortir de cette situation?

Une façon consiste à passer le traitement de ces options directement à la vue que nous créons. Par exemple, nous pouvons transmettre dans notre ProfileView pas une valeur utilisateur spécifique, mais la rendre facultative:

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

Ce code fonctionne, mais pas particulièrement beau. Cela n'a aucun sens de créer un ProfileView pour l'utilisateur nil. Prenons une approche différente: utilisez la carte de notre utilisateur facultatif pour la convertir en ProfileView:

struct HomeView: View {
    @ObservedObject var loginManager: LoginManager

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

C'est déjà beaucoup plus joli: nous n'avons pas besoin de donner manuellement EmptyView lorsque l'utilisateur n'a aucune valeur. Nous pouvons à nouveau transmettre une valeur spécifique à ProfileView, non facultative. Est-il possible de faire encore mieux?

La bonne nouvelle à propos de @ViewBuilder est qu'il ne s'agit pas d'une sorte d'implémentation privée dans SwiftUI, mais d'un attribut accessible avec lequel nous pouvons annoter nos propres fonctions et fermetures.

En utilisant cet attribut, nous pouvons compiler une vue Déballer, qui prend une valeur facultative comme paramètre et une fermeture balisée @ViewBuilder pour convertir une valeur non nulle en Vue:

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

En utilisant cette conception, nous pouvons maintenant complètement repenser la structure entière du 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