@ViewBuilder关闭的默认值?

时间:2020-09-24 13:48:52

标签: swiftui

我们可以给@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。)

结果非常好:

enter image description 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())

结果是:

my view too

如果可以自动推断两个参数,那就太完美了。

---- [[再次编辑] ----

根据@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())

结果是:

my view 3

我已经为那些方便的初始化器实现了所有必需的扩展,它可以工作,但是如果我们能够找到一种避免这些扩展的方法,那将是完美的。

有可能吗?

1 个答案:

答案 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()来生成默认的凹槽和钢筋。