How to pass optional @ViewBuilder parameter in SwiftUI?

My team delivers a UI SDK implemented with UIKit. I'm investigating the best practices for how to ship a framework with reusable UI controls implemented in SwiftUI.


One feature I really like in SwiftUI is @ViewBuilder. I could see one path for us is to ship a TableViewCell (well, the replacement) which has our classic API with String and UILabel-like parameters, but also provide a @ViewBuilder parameter that lets the developer do more significant customizations than we support in the control.


I have this working nicely, where the @ViewBuilder property is required. But, it does not support an optional parameter, which I think is necessary for the struct init(...) method to behave as expected. Per the Swift 5.1 fix, I should be able to init a struct with only the un-inited parameters; I should be able to ignore the optional or pre-defined parameters.


Desired behavior:


ObjectView(title: "Hello, World")


Current behavior:


ObjectView(title: "Hello, World", main: { EmptyView() })


Working example where @ViewBuilder parameter is required.


// UIKit View
class ObjectTableViewCell: UITableViewCell {
   var title: String?
   var titleLabel: UILabel
   var contentView: UIView // inherited from table view cell, gives developer ability to customize layout
}

// SwiftUI View
class ObjectView: View {
    var title: String? = nil
    var titleText: Text? = nil
    private let mainItems: () -> MainItems // custom @ViewBuilder property, gives developer ability to supply custom 'subviews'

    init(title: String? = nil, titleText: Text? = nil, @ViewBuilder main mainItems: @escaping () -> MainItems) {
        self.title = title
        self.titleText = titleText
        self.mainItems = mainItems
    }

    var body: some View {
   
        HStack(alignment: .firstTextBaseline) {
            VStack(alignment: .leading, spacing: 3) {
                if titleText != nil {
                    titleText!
                } else if title != nil {
                    Text(title!)
                } else {
                    mainItems()
                }
            } // + adjacent views ...
        }.padding()
    }
}


Unfortunately, if I modify the definition of the mainItems parameter to (() -> MainItems)?, I get the error: "Function builder attribute 'ViewBuilder' can only be applied to a parameter of function type." Fair enough, but it should be able to account for optionality on that closure parameter.


Post not yet marked as solved Up vote post of sstadelman Down vote post of sstadelman
3.4k views