Search code examples
genericsswiftuienumsviewbinding

Use same view to edit different enums with binding in SwiftUI


I want to use only 1 view to manage 2 pickers in SwiftUI. The data that are binded are enums:

enum ReferenceStockStatus: String, CustomStringConvertible, CaseIterable {
    case all
    case stock
    case notInStock
    
    var description : String {
        switch self {
            case .all: return "All"
            case .stock: return "In stock"
            case .notInStock: return "Not in stock"
        }
    }
}

enum ReferenceGoneStatus: String, CustomStringConvertible, CaseIterable {
    case all
    case takenOut
    case neverGone
    
    var description : String {
        switch self {
            case .all: return "All"
            case .takenOut: return "Taken out"
            case .neverGone: return "Never gone"
        }
    }
}

The parent view:

struct FiltersContextReferenceView: View {
  @ObservedObject var finderViewModel: FinderViewModel
   
  var body: some View {
    
    PickerSegmented(finderViewModel: finderViewModel, selection: $finderViewModel.referenceStockStatus, cases: ReferenceStockStatus.allCases, change: finderViewModel.referenceStockStatus)
    
    PickerSegmented(finderViewModel: finderViewModel, selection: $finderViewModel.referenceGoneStatus, cases: ReferenceGoneStatus.allCases, change: finderViewModel.referenceGoneStatus)

   }
  }

The child view that receives the binding:

struct PickerSegmented: View {

@ObservedObject var finderViewModel: FinderViewModel

@Binding var selection: String // => all enums are string, so I want to use the string type ?!
var cases: Array<String>
var change: String

var body: some View {

    Picker("", selection: $selection) {
        ForEach(cases, id: \.self) { option in
            Text(option.rawValue)
        }
    }
    .onChange(of: change) { _ in
        // do something
    }
    .pickerStyle(SegmentedPickerStyle())
}

}

So, I logically get this error:

Cannot convert value of type 'Binding<ReferenceStockStatus>' to expected argument type 'Binding<String>'

How can I solve the enum type issue ? Using generics ? More generally how to send different data types to edit in the same view ? Here it's a picker, but it could be a List()...


Solution

  • Here is a simple breakdown of how generics would work.

    To start you need to "require" conformance to the protocols the Picker needs.

    CustomStringConvertible & CaseIterable & Hashable
    

    Then just use the available variables

    struct PickerSegmented<P>: View  where P: CustomStringConvertible & CaseIterable & Hashable{
        //tag and selection must match types exactly
        typealias TagType = P
        //Generic type
        @Binding var selection: TagType
        var body: some View {
            Picker("", selection: $selection) {
                //Use custom Case Iterable and Hashable
                ForEach(Array(P.allCases), id: \.description) { option in
                    //Use Custom String Convertible
                    Text(option.description)
                        //tag and selection must match types exactly
                        .tag(option as TagType)
                }
            }
            .pickerStyle(SegmentedPickerStyle())
        }
    }