Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A simple picker to pick a enum.
import SwiftUI
struct EnumPicker<T: Hashable & CaseIterable, V: View>: View {
@Binding var selected: T
var title: String? = nil
let mapping: (T) -> V
var body: some View {
Picker(selection: $selected, label: Text(title ?? "")) {
ForEach(Array(T.allCases), id: \.self) {
mapping($0).tag($0)
}
}
}
}
extension EnumPicker where T: RawRepresentable, T.RawValue == String, V == Text {
init(selected: Binding<T>, title: String? = nil) {
self.init(selected: selected, title: title) {
Text($0.rawValue)
}
}
}
@imthath-m
Copy link

imthath-m commented Jul 17, 2020

It works great.

But in most cases, the raw values of the enums are not good enough to be displayed to the user and in some cases, we have Int as raw values. So I wrote another extension.

extension EnumPicker where T: Displayable, V == Text {
    init(selected: Binding<T>, title: String? = nil) {
        self.init(selected: selected, title: title) {
            Text($0.displayValue)
        }
    }
}

protocol Displayable {
    var displayValue: String { get }
}

@lstomberg
Copy link

lstomberg commented Feb 27, 2021

This works great.

The only change I made is to use Apple's standard CustomStringConvertable protocol instead of Displayable.

extension EnumPicker where T: CustomStringConvertible, V == Text {
    
    init(_ title: String? = nil, selected: Binding<T>) {
        self.init(title: title, selected: selected) {
            Text($0.description)
        }
    }
}

@magnuskahr
Copy link
Author

magnuskahr commented Feb 27, 2021

Thanks @lstomberg!

And yes, it is always great to use things from the Swift Standard Library, good think 💪🏼💪🏼

@apocolipse
Copy link

apocolipse commented Nov 14, 2021

Instead of extensions, you can use a simplified version with a default "displayer", note no need for RawRepresentable, just Hashable and CaseIterable

struct CaseIterablePicker<T: CaseIterable & Hashable> : View
  where T.AllCases: RandomAccessCollection {

  var title: String = ""
  var selection: Binding<T>
  var display: (T) -> String = { "\($0)" }

  var body: some View {
    Picker(title, selection: selection) {
      ForEach(T.allCases, id:\.self) {
        Text(display($0)).tag($0)
      }
    }
  }
}

You can pass closures to display, or KeyPath's with Swift 5.2+, which allows the following, which IMO is better as it simplifies the generic definition greatly and moves the burden on "how to display stuff" to the caller, but in a pretty easy to manage manner

// Simple case, use existing string transforms
enum TestEnum: String, CaseIterable {
  case one, two, three, four, five
}

CaseIterablePicker(selection: $testEnum, display: \.rawValue.capitalized) // Options show as "One", "Two", "Three"...
// Custom extension to String for camel case transform
enum TestEnum2: String, CaseIterable {
  case aTestCase, anotherTestCase, thirdTestCase
}

extension String {
  var camelCaseToReadable: String { ... }
}
CaseIterablePicker(selection: $testEnum2, display: \.rawValue.camelCaseToReadable.capitalizingFirstLetter)
// long chained KeyPath, options show as "A test case", "Another test case", "Third test case"
// Computed var on enum itself is just as simple and doesn't even require RawRepresentable
enum TestEnum3: CaseIterable {
  case someCase, anotherCase, whatever
  
  var localized: LocalizedStringKey { ... }
}

CaseIterablePicker(selection: $testEnum2, display: \.localized)

@magnuskahr
Copy link
Author

magnuskahr commented Nov 14, 2021

Hey @apocolipse!
That is also a take on using enum for pickers. If a simple text view is what always is needed, I think it is a great solution, however it misses the view flexibility of my version :-)

Also, the best of both world can be done! See this extension for my version:

extension EnumPicker where V == Text {
    init(selected: Binding<T>, title: String? = nil, display: @escaping (T) -> String) {
        self.init(selected: selected, title: title) {
            Text(display($0))
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment