Reputation: 36098
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?
Upvotes: 0
Views: 46
Reputation: 4006
Your code is not far from achieving the result you expect. Some comments:
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.Fruit
but the picker-view expects a type Sources.AllCases.Element
: they don't match. Simply use Sources
in your @Binding
.@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)
}
}
}
Upvotes: 1