0

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.

alex.bour
  • 2,842
  • 9
  • 40
  • 66

1 Answers1

1

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))
            }
        }
    }
}
bjorn.lau
  • 774
  • 5
  • 16
  • You idea is good. However, I can't send the value from my viewmodel to the child view: `ButtonNavigationLink(descriptorItem: descriptorItem, selectedItem: $finderViewModel.referenceCountry)` The issue is: Cannot convert value of type 'Binding' to expected argument type 'Binding<(any Item)?>' – alex.bour Dec 14 '22 at 15:19
  • 1
    Setting my Published property to Item instead of Country in my VM makes it working `"@Published var referenceCountry: Item?"` (because of course I use this property as the Binding) Thanks Bjorn ! – alex.bour Dec 14 '22 at 15:39
  • Well, it's a bit hard when I can't see your ViewModel - but I just updated my answer with two buttons that set the selected item accordingly. So you would have to do the same in order for this approach to work :) 1. Set the State to whatever type you want and then pass the State as a binding. I don't know why you need a binding? Because otherwise you could simply just send in your type instead of having to assign and then use. – bjorn.lau Dec 14 '22 at 15:39
  • I just replied a few seconds ago. ;) – alex.bour Dec 14 '22 at 15:41
  • Haha, yea - great you made it work! :D – bjorn.lau Dec 14 '22 at 15:42