我们可以给@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())
结果是:
我已经为那些方便的初始化器实现了所有必需的扩展,它可以工作,但是如果我们能够找到一种避免这些扩展的方法,那将是完美的。
有可能吗?
答案 0 :(得分:1)
这是一种可能方法的演示(您可以替换Gradient
类型)-您需要使用默认init进行扩展以用于特殊类型。
经过测试,可与Xcode 12 / iOS 14一起使用
extension MyView where S == Text, T == Button<Text> {
init() {
self.init(groove: { Text("Hello") },
bar: { Button("World", action: { }) })
}
}
然后可以只使用MyView()
来生成默认的凹槽和钢筋。