Can we give @ViewBuilder closure parameter a default value?
This question arose when I was doing some experiments:
import SwiftUI
import PlaygroundSupport
// MyView
struct MyView<S:View, T:View>: View {
let groove: S
let bar : T
let p : CGFloat = 10 // padding
// ⭐️ no default values
init(@ViewBuilder groove: () -> S, @ViewBuilder bar: () -> T) {
self.groove = groove()
self.bar = bar()
}
var body: some View {
ZStack {
groove
bar.padding(p)
}.frame(height: 80)
}
}
// content view
struct ContentView: View {
var body: some View {
// using MyView
MyView(groove: {
Gradient.down(.gray, .white) // ⭐️ my custom LinearGradient extension
}, bar: {
Gradient.right(.purple, .yellow, .pink) // ⭐️ my custom extension
})
}
}
PlaygroundPage.current.setLiveView(ContentView())
(⭐️ my custom Gradient extension is mentioned here.)
and the result was quite good:
When I tried to go a little further and gave those @ViewBuilder closures default values, everything went bad:
import SwiftUI
import PlaygroundSupport
struct MyView<S:View, T:View>: View {
let groove: S
let bar : T
let p : CGFloat = 10 // padding
// ⭐️ try to give @ViewBuilder closures default values
init(
@ViewBuilder
groove: () -> S = { Gradient.down(.gray, .white) } as! () -> S,
@ViewBuilder
bar: () -> T = { Gradient.right(.purple, .yellow, .pink) } as! () -> T
) {
self.groove = groove()
self.bar = bar()
}
var body: some View {
ZStack {
groove
bar.padding(p)
}.frame(height: 80)
}
}
struct ContentView: View {
var body: some View {
VStack {
// ❌ can't infer `T`
MyView(groove: {
Gradient.down(.gray, .white)
})
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
Type parameter T can not be inferred from the code above.
Any ideas?
----[edited]----
I've made some efforts to my question, and here is what I've got so far:
import SwiftUI
import PlaygroundSupport
// default values for @ViewBuilder parameters
@ViewBuilder var defaultGroove: some View {
Gradient.down(.gray, .white)
}
@ViewBuilder var defaultBar: some View {
Gradient.right(.purple, .yellow, .pink)
}
// MyView
struct MyView<S:View, T:View>: View {
let groove: S
let bar : T
// ⭐️ try to give @ViewBuilder parameters default value
init(
@ViewBuilder groove: () -> S = { defaultGroove as! S },
@ViewBuilder bar : () -> T = { defaultBar as! T }
) {
self.groove = groove()
self.bar = bar()
}
var body: some View {
ZStack {
groove
bar.padding(10)
}.frame(height: 80)
}
}
// Content View
struct ContentView: View {
var body: some View {
VStack {
// `bar` omitted
MyView<LinearGradient, LinearGradient>(groove: {
Gradient.bottomRight(.white, .gray, .white)
})
// `groove` omitted
MyView<LinearGradient, Color>(bar: {
Color.pink
})
// both omitted
MyView<LinearGradient, LinearGradient>()
// ❌ can't infer `S`
// ⭐️ it would be perfect if `S` can be inferred.
// MyView(bar: {
// Gradient.right(.purple, .white)
// })
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
and the result is:
It would be perfect if both parameters could be inferred automatically.
----[ edited again ]----
According to @Asperi's previous advise, I've made my third attempt:
import SwiftUI
import PlaygroundSupport
// default values for @ViewBuilder parameters
@ViewBuilder var defaultGroove: some View {
Gradient.down(.gray, .white)
}
@ViewBuilder var defaultBar: some View {
Gradient.right(.purple, .yellow, .pink)
}
// MyView
struct MyView<S:View, T:View>: View {
let groove: S
let bar : T
// default value for @ViewBuilder parameters
init(
@ViewBuilder groove: () -> S = { defaultGroove as! S },
@ViewBuilder bar: () -> T = { defaultBar as! T }
) {
self.groove = groove()
self.bar = bar()
}
var body: some View {
ZStack {
groove
bar.padding(10)
}.frame(height: 80)
}
}
// ⭐️ conditional extensions for convenience inits
extension MyView where T == LinearGradient {
/// MyView(groove:)
init(@ViewBuilder groove: () -> S){
self.init(
groove: groove,
bar : { defaultBar as! T }
)
}
}
extension MyView where S == LinearGradient {
/// MyView(bar:)
init(@ViewBuilder bar: () -> T){
self.init(
groove: { defaultGroove as! S },
bar : bar
)
}
}
extension MyView where S == LinearGradient, T == LinearGradient {
/// MyView()
init(){
self.init(
groove: { defaultGroove as! S },
bar : { defaultBar as! T }
)
}
}
// Content View
struct ContentView: View {
var body: some View {
VStack {
// ⭐️ `S`, `T` are both inferred in the following cases
MyView(groove: {
Gradient.bottomRight(.white, .yellow, .green)
}, bar: {
Color(white: 0.8)
.shadow(color: .black, radius: 3, x: 3, y: 3)
.shadow(color: .white, radius: 3, x: -3, y: -3)
})
// `bar` omitted
MyView(groove: {
Gradient.right(.red, .purple)
})
// `groove` omitted
MyView(bar: {
Gradient.right(.purple, .white)
.shadow(color: .black, radius: 3, x: 0, y: 2)
})
// `groove`, `bar` both omitted
MyView()
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
and the result is:
I've implemented all the needed extensions for those convenience intializers, it works but if we could find a way to avoid those extensions in the first place, it would be perfect.
Is it possible?


