Search code examples
genericsswiftuistructview

Edit different structs/models in the same View in SwiftUI


I didn't find any posts/ways to load/manage different objects in the same view in SwiftUI. The goal is to not multiply same code.

Supposing I have 3 structs/instances Country, Region, Subregion with same properties: id, name, image.

struct Country: Identifiable, Codable, Hashable {       
  var id: Int64?
  var name: String
  var image: String   
}

struct Region: Identifiable, Codable, Hashable {        
  var id: Int64?
  var name: String
  var image: String   
}

struct Subregion: Identifiable, Codable, Hashable {     
  var id: Int64?
  var name: String
  var image: String   
}

In a a parent view, I want to send instances to the child view as a binding:

ButtonNavigationLink(descriptorItem: descriptorItem, selectedItem: $finderViewModel.referenceCountry)
ButtonNavigationLink(descriptorItem: descriptorItem, selectedItem: $finderViewModel.referenceRegion)
ButtonNavigationLink(descriptorItem: descriptorItem, selectedItem: $finderViewModel.referenceSubregion)

Note: I don't want to send the same properties to the child view, this is easy. I want to send the struct itself (model). I need it in the child view.

So now, I want to receive the model/struct in the child view. I have done multiple try such:

  • using generics: @Binding var selectedItem: GenericType? => the problem is I don't know how to read the properties from the generic (cast ?)
  • trying AnyHashable: @Binding var selectedItem: AnyHashable? => the compiler is lost between the type sent (country) and AnyHashable type

Well, I am lost. :-)

My subview that receive the "generic" binding looks like that:

// struct ButtonNavigationLink <T>: View where T: Hashable { // => if generic
struct ButtonNavigationLink: View {
        
    var descriptorItem: DescriptorItem
    
    @Binding var selectedItem: AnyHashable?

    // Generic type
    //@Binding var selectedItem: GenericType?   
    //typealias GenericType = T
}

So what's the best solution to not multiply views for each object ? The child view goal is to select the item in the @Binding, not only to display data... that's why I need to full object and not only the properties as @Binding.

Button (action: {
  selectedItem = descriptorItem
 }) 

Thanks in advance for any help/suggestion.


Solution

  • The most obvious way to do this is to define a protocol:

    protocol Item {
        var id: Int64? { get }
        var name: String { get }
        var image: String { get }
    }
    

    Then make your structs conform to that protocol:

    struct Country: Identifiable, Codable, Hashable, Item {
    struct Region: Identifiable, Codable, Hashable, Item {
    struct Subregion: Identifiable, Codable, Hashable, Item {
    

    And finally, change your binding to use the protocol type:

    struct DetailsView: View {
    
        @Binding var selectedItem: Item?
    
        var body: some View {
            VStack {
                if let selectedItem {
                    if selectedItem is Region { // <- If you want to check which type was passed
                        Text("Region name: \(selectedItem.name)")
                    }
                }
            }
        }
    }
    
    
    struct ContentView: View {
    
        @State var selectedItem: Item?
    
        var body: some View {
    
            VStack {
                Button("Set to Region") {
                    selectedItem = Region(id: 1, name: "region", image: "Image")
                }
    
                Button("Set to Country") {
                    selectedItem = Country(id: 0, name: "contry", image: "Image")
                }
    
                NavigationStack {
                    NavigationLink("Pass \(selectedItem?.name ?? "nil")", destination: DetailsView(selectedItem: $selectedItem))
                }
            }
        }
    }