How to use SwiftUI @ViewBuilder to create views which require parameters?

Viewed 52

In the answers to my question on How to add a generic SwiftUI view to another view, I learned that this can be done using @ViewBuilder.

While this works fine for most of my use cases, I now came across a new problem:

  • The @ViewBuilder solution basically creates the ContentView view outside the GenericView<Content: View>
  • The ContentView is than passed to the GenericView<Content: View> which shows it

BUT: What if the ContentView has to be created inside GenericView<Content: View> because it requires some parameters which are only available there?


Example:

  • UserView is created by providing a user ID
  • UserViews view model fetches the user name using the ID. So the which creates the UserView does only know the ID but not the name. The name is only available within UserView
  • UserView is used at different places within an app or even in different apps. The different places require to show the username in different layouts/styles/etc. To not hard code all layouts into UserView, the view is generic and is given a Content view type which is used to show the username

Code

protocol NameView: View {
    init(name: String)
}

struct NameOnlyView: NameView {
    private let name: String
    
    init(name: String) {
        self.name = name
    }
    
    var body: some View {
        Text(name)
    }
}

struct NameGreetingView: NameView {
    private let name: String
    
    init(name: String) {
        self.name = name
    }
    
    var body: some View {
        Text("Hello \(name)")
    }
}

struct UserView<Content: NameView>: View {
    private let name: String
    private let nameView: Content
    
    init(userId: Int, @ViewBuilder nameViewBuilder: (String) -> Content) {
        name = LoadUserName(usingId: userId)
        nameView = nameViewBuilder(name)
    }
    
    var body: some View {
        nameView
    }
}


struct SomeView: View {
    var body: some View {
        // What I would like to do
        UserView(userId: 123, NameOnlyView)
        UserView(userId: 987, NameGreetingView)

        // What is required instead
        UserView(userId: 123) {
            NameOnlyView("Name which is not known here")
        }
    }
} 

Of course I could move the logic to load the username from the given ID and make it available in SomeView. However, this is just an example for any value which is only available in UserView but not in SomeView. Especially when using the UserView in different apps I do not want to implement the same logic to load the username (or whatever) in all possible parent views of UserView.

Can this be solved using the @ViewBuilder solution?

Can this be solved in SwiftUI at all?

Or am I completely on the wrong track?

1 Answers
Related