genericsswiftuiview

A generic SwiftUI view for TableColumn


In the SwiftUI documentation I found this table:

struct Purchase: Identifiable {
    let price: Decimal
    let id = UUID()
}

struct TipTable: View {
    let currencyStyle = Decimal.FormatStyle.Currency(code: "USD")
    
    var body: some View {
        Table(of: Purchase.self) {
            TableColumn("Base price") { purchase in
                Text(purchase.price, format: currencyStyle)
            }
            TableColumn("With 15% tip") { purchase in
                Text(purchase.price * 1.15, format: currencyStyle)
            }
            TableColumn("With 20% tip") { purchase in
                Text(purchase.price * 1.2, format: currencyStyle)
            }
            TableColumn("With 25% tip") { purchase in
                Text(purchase.price * 1.25, format: currencyStyle)
            }
        } rows: {
            TableRow(Purchase(price: 20))
            TableRow(Purchase(price: 50))
            TableRow(Purchase(price: 75))
        }
    }
}

I thought that it could use a generic TableCoulumnView to be called like TableColumnView("Base price", 1, purchase)

So that TipTable can become:

struct TipTable: View {
    let currencyStyle = Decimal.FormatStyle.Currency(code: "USD")
    
    var body: some View {
        Table(of: Purchase.self) {
            TableColumnView("Base price", 1, purchase)
            TableColumnView("With 15% tip", 1.15, purchase)
            TableColumnView("With 20% tip", 1.2, purchase)
            TableColumnView("With 25% tip", 1.25, purchase)
        } rows: {
            TableRow(Purchase(price: 20))
            TableRow(Purchase(price: 50))
            TableRow(Purchase(price: 75))
        }
    }
}

so I tried this:

    struct TableColumnView : View {
        var title: String
        var purchase: Purchase
        var factor: Double
        var body: some View {
            TableColumn(title) { purchase in
                Text(purchase.price * factor, format: currencyStyle)
            }
        }
    }

But it gave the error Cannot infer type of closure parameter 'purchase' without a type annotation .

What else is needed to make it work? I think some type annotations need to be added.


Solution

  • You can create a function that returns a TableColumnContent, this type is normally used together with a TableColumnBuilder.

    func ExampleColumn(title: String, factor: Decimal) -> some TableColumnContent<Purchase, Never> {
        TableColumn(title) {
            Text($0.price * factor, format: currencyStyle)
        }
    }
    

    The second part of the generic return type is for sorting that is not supported here using Never

    So the table would then be created as

    Table(of: Purchase.self) {
        ExampleColumn(title: "Base price", factor: 1)
        ExampleColumn(title: "With 15% tip", factor: 1.2)
        ExampleColumn(title: "With 20% tip", factor: 1.25)
        ExampleColumn(title: "With 25% tip", factor: 1.5)
    } rows: {
        TableRow(Purchase(price: 20))
        TableRow(Purchase(price: 50))
        TableRow(Purchase(price: 75))
    }