ios 在ViewBuilder关闭时可选

vvppvyoh  于 2022-11-19  发布在  iOS
关注(0)|答案(5)|浏览(116)

SwiftUI中是否可以有一个可选的@ViewBuilder闭包?例如,假设我想开发一个自定义视图,它采用两个视图构建器闭包,如下所示:

import SwiftUI

struct TopAndBottomView<Content>: View where Content: View {
    let topContent: () -> Content
    let bottomContent: () -> Content

    init(@ViewBuilder topContent: @escaping () -> Content, @ViewBuilder bottomContent: @escaping () -> Content) {
        self.topContent = topContent
        self.bottomContent = bottomContent
    }

    var body: some View {
        VStack {
            topContent()
            Spacer()
            bottomContent()
        }
    }
}

struct TopAndBottomView_Previews: PreviewProvider {
    static var previews: some View {
        TopAndBottomView(topContent: {
            Text("TOP")
        }, bottomContent: {
            Text("BOTTOM")
        })
    }
}

但我希望底部视图是可选的。我尝试了:

struct TopAndBottomView<Content>: View where Content: View {
    let topContent: () -> Content
    let bottomContent: (() -> Content)?

    init(@ViewBuilder topContent: @escaping () -> Content, @ViewBuilder bottomContent: (() -> Content)? = nil) {
        self.topContent = topContent
        self.bottomContent = bottomContent
    }

    var body: some View {
        VStack {
            topContent()
            Spacer()
            if bottomContent != nil {
                bottomContent!()
            }
        }
    }
}

但是我得到了这个错误:
函数生成器属性“ViewBuilder”只能应用于函数类型的参数。

  • 谢谢-谢谢
6yoyoihd

6yoyoihd1#

考虑到ViewBuilderbuildIf特性,以下方法可能允许将ViewBuilder保留在init中(这是首选)
已通过测试并可与Xcode 11.2 / iOS 13.2兼容

struct TopAndBottomView<Content>: View where Content: View {
    let topContent: () -> Content
    let bottomContent: () -> Content?

    init(@ViewBuilder topContent: @escaping () -> Content, 
         @ViewBuilder bottomContent: @escaping () -> Content? = { nil }) {
        self.topContent = topContent
        self.bottomContent = bottomContent
    }

    var body: some View {
        VStack {
            topContent()
            Spacer()
            bottomContent()
        }
    }
}

所以和这个一样

struct TopAndBottomView_Previews: PreviewProvider {
    static var previews: some View {
        TopAndBottomView(topContent: {
            Text("TOP")
        }, bottomContent: {
            Text("BOTTOM")
        })
    }
}

还有这个

struct TopAndBottomView_Previews: PreviewProvider {
    static var previews: some View {
        TopAndBottomView(topContent: {
            Text("TOP")
        })
    }
}
jecbmhm3

jecbmhm32#

@JoeBayLD问道:
如果topContent和bottomContent是不同的视图类型,您将如何执行此操作?我创建了一个新的泛型属性,但是当使用默认的'nil'参数时,任何调用者都无法推断内容类型
您可以将两个ViewBuilder参数都设为 * 非选择性 *,然后将副档名设为where BottomContent == EmptyView来行程“无底部内容”的情况:

struct TopAndBottomView<TopContent: View, BottomContent: View>: View {
    let topContent: TopContent
    let bottomContent: BottomContent

    init(@ViewBuilder topContent: () -> TopContent,
         @ViewBuilder bottomContent: () -> BottomContent) {
        self.topContent = topContent()
        self.bottomContent = bottomContent()
    }

    var body: some View {
        VStack {
            topContent
            Spacer()
            bottomContent
        }
    }
}

extension TopAndBottomView where BottomContent == EmptyView {
    init(@ViewBuilder topContent: () -> TopContent) {
        self.init(topContent: topContent, bottomContent: { EmptyView() })
    }
}

// usage

TopAndBottomView(topContent: { Text("hello") })

TopAndBottomView(topContent: { Text("hello") }, bottomContent: { Text("world") })
rbl8hiat

rbl8hiat3#

在 这个 来自 Sundell 的 奇妙 的 post 中 , 他 建议 我们 构建 一 个 自 定义 的 structUnwrap 来 展开 一 个 可选 值 并 将 其 转换 为 View , 下面 的 代码 是 他 在 那 篇 文章 中 所 做 的 :

import SwiftUI

/// # Unwrap
/// unwraps a value (of type `Value`) and turns it 
/// into `some View` (== `Optional<Content>`).
struct Unwrap<Value, Content: View>: View {
    
    private let value  : Value?               // value to be unwrapped
    private let content: (Value) -> Content   // closure: turn `Value` into `Content`
    
    init(
        _ value: Value?,
         @ViewBuilder content: @escaping (Value) -> Content  // ⭐️ @ViewBuilder
    ) {
        self.value   = value
        self.content = content
    }
    
    var body: some View {   
        // map: (by the closure `content`)
        // nil (Optional<Value>.none)  -> nil (Optional<Content>.none)
        // Optional<Value>.some(Value) -> Optional<Content>.some(Content)
        value.map(content)  // Optional<Content>
    }
}

中 的 每 一 个
然后 , 我 编写 了 一些 代码 来 演示 如何 使用 Unwrap 来 构造 视图 :

import SwiftUI

// MyView
struct MyView: View {
    
    @State private var isValue1Nil = false
    @State private var isValue2Nil = false
    
    var value1: Int? { isValue1Nil ? nil : 1}
    var value2: Int? { isValue2Nil ? nil : 2}
    
    var body: some View {
        VStack {
            
            // stack of `Unwrap`s
            VStack {
                // ⭐️ `Unwrap` used here.
                Unwrap(value1) {
                    Color.red.overlay(Text("\($0)"))
                }
                Unwrap(value2) {
                    Color.orange.overlay(Text("\($0)"))
                }
            }.border(Color.blue, width: 3)
                
            // toggles
            HStack {
                Toggle(isOn: $isValue1Nil) {
                    Text("value1 is nil")
                }
                Toggle(isOn: $isValue2Nil) {
                    Text("value2 is nil")
                }
                Spacer()
            }
                .padding()
                .overlay(Rectangle().stroke(Color.gray, style: .init(dash: [6])))
                
        } // VStack (container)
            .padding()
            .border(Color.gray, width: 3)
    }
}

格式
结果 如下 :

        • [ 编辑 ] - - - -
          或者 , 我们 可以 创建 一 个 View 扩展 来 完成 这项 工作 :
// view.ifLet(_:then:)
extension View {
    @ViewBuilder func ifLet<Value, Content: View>(
        _ value: Value?, 
        @ViewBuilder then modifySelfWithValue: (Self, Value) -> Content 
    ) -> some View {
        if value != nil {
            modifySelfWithValue(self, value!)
        } else { self }
    }
}

格式
下面 是 有关 如何 使用 此 扩展 的 另 一 个 演示 :

struct ContentView: View {
    
    @State private var isNil = false
    var value: Int? { isNil ? nil : 2 }
    
    var body: some View {
        VStack {
            
            Color.red.overlay(Text("1"))
                // ⭐️ view.ifLet(_:then:)
                .ifLet(value) { (thisView, value) in
                    // construct new view with `thisView` and `value`
                    VStack {
                        thisView
                        Color.orange.overlay(Text("\(value)"))
                    }
            } // view modified by `ifLet`
                .border(Color.blue, width: 3)
                
            // toggles
            Toggle(isOn: $isNil) { Text("value is nil") }
                .padding()
                .overlay(Rectangle().stroke(Color.gray, style: .init(dash: [6])))
            
        } // VStack (container)
            .padding()
            .border(Color.gray, width: 3).frame(height: 300)
    }
}

格式
结果 为 :

py49o6xq

py49o6xq4#

看起来您不需要在初始化器中使用@ViewBuilder,这样就可以正常工作:

struct TopAndBottomView<Content>: View where Content: View {
    let topContent: () -> Content
    let bottomContent: (() -> Content)?

    init(@ViewBuilder topContent: @escaping () -> Content, bottomContent: (() -> Content)?) {
        self.topContent = topContent
        self.bottomContent = bottomContent
    }

    var body: some View {
        VStack {
            topContent()
            Spacer()

            if bottomContent != nil {
                bottomContent!()
            }
        }
    }
}
qyyhg6bp

qyyhg6bp5#

为@ViewBuilder视图设置默认值,以达到你所寻找的效果:

struct AlertView<InputFields: View, Actions: View>: View {
    private let inputFields: InputFields
    private let actions: Actions

    init(
        @ViewBuilder inputFields: () -> InputFields = { EmptyView() }, <=== HERE
        @ViewBuilder actions: () -> Actions = { EmptyView() } <=== HERE
    ) {
        self.inputFields = inputFields()
        self.actions = actions()
    }

    var body: some View {
        VStack{
            inputFields
            actions
        }
    }
}

相关问题