简体   繁体   中英

How to get siblings of enum case to build custom picker?

I'm trying to build a SwiftUI that accepts any enum case as its selection, then it will automatically render its siblings as options. Here's what I started with, but I'm having a hard time overcoming the generics:

enum Option: String, CaseIterable, Identifiable, Equatable {
    case abc
    case def
    case ghi
    var id: Self { self }
}

struct CustomPicker<Sources>: View where Sources: RawRepresentable & CaseIterable & Identifiable, Sources.AllCases: RandomAccessCollection, Sources.RawValue == String {
    @Binding var selection: Sources.AllCases.Element

    var body: some View {
        ScrollView(.horizontal) {
            HStack(spacing: 8) {
                ForEach(Sources.allCases) { item in
                    Text(item.rawValue)
                }
            }
            .padding(.horizontal)
        }
    }
}

When I try to use it, I get a compile error: Generic parameter 'Sources' could not be inferred :

enum Fruit: String, CaseIterable, Identifiable, Equatable {
    case apple, banana, orange
}

struct ContentView: View {
    @State private var selectedFruit: Fruit = .apple // Error here

    var body: some View {
        CustomPicker(selection: $selectedFruit) // Error here
    }
}

How can I get the generics correct to be able to handle any String/RawRepresentable enum and build out its siblings automatically?

Your code is not far from achieving the result you expect. Some comments:

  • You don't need to require that Sources conform to Identifiable : it needs to be Hashable to work with ForEach , then force the ForEach to use \.self as the id. The raw value of the enum is of type String (you required it), using \.self will always work.
  • Fruit is not Identifiable and it doesn't need to be.
  • The main view is passing Fruit but the picker-view expects a type Sources.AllCases.Element : they don't match. Simply use Sources in your @Binding .
  • Remember to update the value of the @Binding .

Here below you have a working example:

struct ContentView: View {
    @State private var selectedFruit: Fruit = .apple
    
    @State private var selectedCar = Car.ferrari

    var body: some View {
        VStack {
            CustomPicker(selection: $selectedFruit)
            
            CustomPicker(selection: $selectedCar)
            
            // Just for testing
            Text("Eating one \(selectedFruit.rawValue) in a \(selectedCar.rawValue)")
                .font(.largeTitle)
                .padding()
        }
    }
}

// The Enum doesn't need to be Identifiable
enum Fruit: String, CaseIterable {
    case apple, banana, orange
}

enum Car: String, CaseIterable {
    case ferrari, porsche, jaguar, dacia
}

// Replace Identifiable with Hashable
struct CustomPicker<Sources>: View where Sources: RawRepresentable & CaseIterable & Hashable, Sources.AllCases: RandomAccessCollection, Sources.RawValue == String {
    
    @Binding var selection: Sources    // This is the right type

    var body: some View {
        ScrollView(.horizontal) {
            HStack(spacing: 8) {
                
                // Force the id as the item itself
                ForEach(Sources.allCases, id: \.self) { item in
                    Text(item.rawValue)
                    
                        // Somehow you need to update the @Binding
                        .onTapGesture {
                            selection = item
                        }
                }
            }
            .padding(.horizontal)
        }
    }
}

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