简体   繁体   中英

Passing @Binding variable from protocol var to if in SwiftUI

I have a model like this:

protocol PurchasableProduct {
    var randomId: String { get }
}

class Cart: Identifiable {
    var items: [PurchasableProduct]
    
    init(items: [PurchasableProduct]) {
        self.items = items
    }
}

class Product: Identifiable, PurchasableProduct {
    var randomId = UUID().uuidString
    var notes: String = ""
}

class DigitalGood: Identifiable, PurchasableProduct {
    var randomId = UUID().uuidString
}

where items conform to protocol PurchasableProduct .

I want to build a View that shows cart like this:

struct CartView: View {
    @State var cart: Cart
    
    var body: some View {
        List {
            ForEach(cart.items.indices) { index in
                CartItemView(item: self.$cart.items[index])
            }
        }
    }
}

where CartItemView is:

struct CartItemView: View {
    @Binding var item: PurchasableProduct
    
    var body: some View {
        VStack {
            if self.item is Product {
                Text("Product")
            } else {
                Text("Digital Good")
            }
        }
    }
}

That's working and give me result as This (screenshot)

But I want to extend this a but more that my items element can be passed as a binding variable lets say as:

struct CartItemView: View {
    @Binding var item: PurchasableProduct
    
    var body: some View {
        VStack {
            if self.item is Product {
                VStack {
                    TextField("add notes", text: (self.$item as! Product).notes) // ❌ Cannot convert value of type 'String' to expected argument type 'Binding<String>'
                    TextField("add notes", text: (self.$item as! Binding<Product>).notes) // ⚠️ Cast from 'Binding<PurchasableProduct>' to unrelated type 'Binding<Product>' always fails
                }
            } else {
                Text("Digital Good")
            }
        }
    }
}

What I'm trying to achieve is:

  1. I have a collection of items that depends on a class should be drawn differently
  2. Items have different editable sync that should be binded into CartView

Not sure if thats syntax issue or my approach issue... how to cast this on body to get the correct view based on type?

You may create a custom binding:

struct CartItemView: View {
    @Binding var item: PurchasableProduct

    var product: Binding<Product>? {
        guard item is Product else { return nil }
        return .init(
            get: {
                self.$item.wrappedValue as! Product
            }, set: {
                self.$item.wrappedValue = $0
            }
        )
    }
    var body: some View {
        VStack {
            if product != nil {
                TextField("add notes", text: product!.notes)
            } else {
                Text("Digital Good")
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM