Tengo una matriz de protocolo @Published que estoy recorriendo con un ForEach para presentar los elementos en alguna vista. Me gustaría poder usar la sintaxis enlazable de SwiftUI con ForEach para generar un enlace para mí, de modo que pueda mutar cada elemento y reflejarlo en la matriz original.
Esto parece funcionar para las propiedades que se implementan en el protocolo, pero no estoy seguro de cómo accedería a las propiedades que son exclusivas del tipo de conformidad del protocolo. En el código de ejemplo a continuación, sería la propiedad de owner del Animal o la propiedad de age del Human . Pensé que podría ser necesario algún tipo de conversión de tipo, pero no puedo entender cómo retener la referencia a la matriz subyacente a través del enlace.
Avísame si necesitas más detalles.
import SwiftUI protocol Testable { var id: UUID { get } var name: String { get set } } struct Human: Testable { let id: UUID var name: String var age: Int } struct Animal: Testable { let id: UUID var name: String var owner: String } class ContentViewModel: ObservableObject { @Published var animalsAndHumans: [Testable] = [] } struct ContentView: View { @StateObject var vm: ContentViewModel = ContentViewModel() var body: some View { VStack { ForEach($vm.animalsAndHumans, id: \AnyTestable.id) { $object in TextField("textfield", text: $object.name) // if the object is an Animal, how can I get it's owner? } Button("Add animal") { vm.animalsAndHumans.append(Animal(id: UUID(), name: "Mick", owner: "harry")) } Button("Add Human") { vm.animalsAndHumans.append(Human(id: UUID(), name: "Ash", age: 26)) } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }Este es un problema espinoso con sus tipos de datos.
Si puede cambiar sus tipos de datos, puede hacer que esto sea más fácil de resolver.
Por ejemplo, tal vez pueda modelar sus datos de esta manera, usando una enum en lugar de un protocol para representar las variantes:
struct Testable { let id: UUID var name: String var variant: Variant enum Variant { case animal(Animal) case human(Human) } struct Animal { var owner: String } struct Human { var age: Int } }También ayudará agregar descriptores de acceso para los datos asociados de las dos variantes:
extension Testable { var animal: Animal? { get { guard case .animal(let animal) = variant else { return nil } return animal } set { guard let newValue = newValue, case .animal(_) = variant else { return } variant = .animal(newValue) } } var human: Human? { get { guard case .human(let human) = variant else { return nil } return human } set { guard let newValue = newValue, case .human(_) = variant else { return } variant = .human(newValue) } } }Entonces puedes escribir tu vista así:
class ContentViewModel: ObservableObject { @Published var testables: [Testable] = [] } struct ContentView: View { @StateObject var vm: ContentViewModel = ContentViewModel() var body: some View { VStack { List { ForEach($vm.testables, id: \.id) { $testable in VStack { TextField("Name", text: $testable.name) if let human = Binding($testable.human) { Stepper("Age: \(human.wrappedValue.age)", value: human.age) } else if let animal = Binding($testable.animal) { HStack { Text("Owner: ") TextField("Owner", text: animal.owner) } } } } } HStack { Button("Add animal") { vm.testables.append(Testable( id: UUID(), name: "Mick", variant: .animal(.init(owner: "harry")) )) } Button("Add Human") { vm.testables.append(Testable( id: UUID(), name: "Ash", variant: .human(.init(age: 26)) )) } } } } }Una forma simple de resolver es extender su protocolo Testable . algo le gusta
protocol Testable { var id: UUID { get } var name: String { get set } var extra: String? { get } } struct Human: Testable { let id: UUID var name: String var age: Int var extra: String? { nil } } struct Animal: Testable { let id: UUID var name: String var owner: String var extra: String? { return owner } } Su bloque ForEach no necesita saber el tipo concreto: Animal o Human , solo verifique el extra de Testable para decidir agregar un nuevo elemento o no.