Pass SwiftUI view as argument to another view
I assume since you are reading this article that you want to be able to pass a view or multiple views as an argument to another view using SwiftUI. In this tutorial we will learn how we can use ViewBuilder to achieve this.
Step 1: Create a custom view
This custom view is going to be super basic. The custom view is going to contain a VStack
that will have a red border. Add the following code to your project(All my code will be done in the ContentView
):
struct BorderedView: View {
var body: some View {
VStack {
Text("Bordered View")
}.border(Color.red,
width: 2)
}
}
I have updated my ContentView
to look like this:
struct ContentView: View {
var body: some View {
BorderedView()
}
}
If I build and run the app now, it looks like this:
Ok, now that our custom view works, let's update it to allow us to pass one or more views as an argument!
Step 2: Allow BorderedView to take views as argument/parameter
To do this we need to update the BorderedView
to look like the following:
struct BorderedView<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack {
self.content
}.border(Color.red,
width: 2)
}
}
Ok, so what have we done here?
We have added a content
property. This property will work as any other property, but we are using a Content
type. The reason for this is because we cannot use View
directly. If we try to use View
directly instead of using generics, we will get the following error:
Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
Next, we have created an initialiser that will take a closure that returns a type of Content
which we know needs to conform to the View
protocol, but we also have @ViewBuilder
.
What is @ViewBuilder
? ViewBuilder
is a functionBuilder
, if you want to read up about these, you can take a look at this article by Vadim Bulavin.
Apple defines a ViewBuilder
as:
A custom parameter attribute that constructs views from closures
This means that we can use the ViewBuilder
parameter attribute on closure parameters/arguments to allow the close to provide multiple child views. See docs.
What other updates have we made to our custom view? We call the content
closure when we assign it to our content
property, and then we replaced the Text
view in the VStack
with the content
property. This will allow us to use any views that gets passed in as arguments as child views of our VStack
.
Step 3: Pass views to BorderedView
To see this in action, we need to update the ContentView
code to look like the following:
struct ContentView: View {
var body: some View {
BorderedView {
Text("Text view in CustomView")
Button(action: {
print("Button tapped")
}, label: {
Text("My button label")
})
}
}
}
In the above code were are still using BorderedView
, but this time we need to pass views in as arguments. For this example I have passed in two views, specifically a Text
view, as well as a Button
. If we build and run the app, we should see the following:
Conclusion
SwiftUI and ViewBuilder make it very easy to create custom views that can take other views as an argument. This can be super powerful when composing views.
If you want to see the full source code, you can find it here.