我们可以给@ViewBuilder闭包参数一个默认值吗?
当我做一些实验时,出现了这个问题:
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())(我的自定义Gradient扩展名为here⭐️。)
结果相当不错:

当我尝试更进一步并为这些@ViewBuilder闭包提供默认值时,一切都变得糟糕:
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())无法从上面的代码推断出类型参数T。有什么想法吗?
-编辑
我已经为我的问题做了一些努力,到目前为止我得到了以下结果:
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())结果是:

如果这两个参数都能自动推断出来,那就太好了。
-再次编辑
根据@Asperi的previous advise,我已经做了第三次尝试:
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())结果是:

我已经为那些方便的intializer实现了所有需要的扩展,它是有效的,但如果我们能找到一种方法在第一时间避免这些扩展,那就太好了。
有可能吗?
发布于 2020-09-24 22:27:16
这是一个可能的方法的演示(你可以替换你的Gradient类型)-你需要用默认的初始化来扩展特殊的类型。
使用Xcode12/ iOS 14进行了测试
extension MyView where S == Text, T == Button<Text> {
init() {
self.init(groove: { Text("Hello") },
bar: { Button("World", action: { }) })
}
}然后你可以使用MyView(),它会生成你默认的凹槽和条形。
https://stackoverflow.com/questions/64047904
复制相似问题