我如何在SwiftUI视图中有一个辅助的可选ViewBuilder?

oyjwcjzk  于 2023-05-16  发布在  Swift
关注(0)|答案(1)|浏览(198)

这是我目前拥有的代码。基本上,我想有一个可选的ViewBuilder添加一个条件视图,让说一个附件视图。在一种.overlay语法中,你可以有也可以没有覆盖。但是在插入视图时,我得到了这个错误Cannot convert value of type '' to closure result type 'Content'

public struct MyView<Content: View>: View {

    @State var isHidden: Bool = false
    // stuff here
    let content: (Int) -> Content
    var additionalView: (() -> Content)?
    
    public init(@ViewBuilder content: @escaping (Int) -> Content, additionalView: (() -> Content)? = nil) {
        self.content = content
        self.additionalView = additionalView
    }
    
    public var body: some View {
        VStack(spacing: 0) {
            ZStack {
                content(selection)
            }
            // more stuff
            
            if !isHidden {
                if let additionalView {
                    additionalView()
                }
                // Another view
            }
        }
    }
    
    public func accessoryView(@ViewBuilder content: @escaping () -> Content) -> some View {
        var view = self
        view.additionalView = content
        return view
    }
}

最后我想这样称呼它。

struct MyMainView<Content: View>: View {
    @StateObject var appInfo: AppInfo = AppInfo()
    @ViewBuilder let content: (Int) -> Content

    var body: some View {
        VStack(spacing: 0) {
            MyView(content: content)
                .accessoryView {
                    AnyKindOfView()
                // This is where it fails with "Cannot convert value of type 'AnyKindOfView' to closure result type 'Content'"
                }
                .onAppear {
                    //code
                }
        }
        .environmentObject(appInfo)
    }
}

我错过了什么?我试图创建一个额外的,<AdditionalContent: View>但它似乎不工作。

rta7y2nd

rta7y2nd1#

您确实应该使用一个额外的类型参数来标识附件视图的类型。附件视图不一定与Content相同。这就是一些内置SwiftUI视图的设计方式,比如Label有两个类型参数来表示标题和图标。

public struct MyView<Content: View, Accessory: View>: View {
    // ...
    var additionalView: (() -> Accessory)?

    // ...
    public init(@ViewBuilder content: @escaping (Int) -> Content, additionalView: (() -> Accessory)?) {
        // ...
    }
}

extension MyView where Accessory == Never {

    public init(@ViewBuilder content: @escaping (Int) -> Content) {
        self.init(content: content, additionalView: nil)
    }
}

请注意,可选参数使Swift很难推断Accessory类型,所以我删除了它,并在Accessory == Never的扩展中添加了一个单参数init。同样,这类似于SwiftUI的内置视图与可选视图构建器的工作方式。例如,不接受label视图构建器的Button初始化器在扩展中声明,其中Label == Text
对于accessoryView修饰符,它也需要是通用的。它应该返回一个新的MyView,带有一个不同的Accessory类型参数。

public func accessoryView<NewAccessory: View>(@ViewBuilder content: @escaping () -> NewAccessory) -> some View {
    MyView<Content, NewAccessory>(content: self.content, additionalView: content)
}

不过,我觉得没这个必要。为什么不直接将视图传递给初始化器的additionalView参数呢?
另一种方法是使用AnyView作为附件视图的类型。这涉及到使init的通用以及。

public struct MyView<Content: View>: View {
    // ...
    var additionalView: (() -> AnyView)?

    public init<Accessory: View>(@ViewBuilder content: @escaping (Int) -> Content, additionalView: (() -> Accessory)?) {
        self.content = content
        if let additionalView {
            self.additionalView = { AnyView(additionalView()) }
        }
    }

    // ...

    public func accessoryView<Accessory: View>(@ViewBuilder content: @escaping () -> Accessory) -> some View {
        var view = self
        view.additionalView = { AnyView(content()) }
        return view
    }
}

extension MyView {

    public init(@ViewBuilder content: @escaping (Int) -> Content) {
        self.init(content: content, additionalView: nil as (() -> Never)?)
    }
}

相关问题