SwiftUI Picker — warum Menu das bessere Label hat
Ursprünglich erschienen auf Medium: Setup SwiftUI Picker with a label (Juli 2024) und Create Generics Enum PickerView (Januar 2025).
Der SwiftUI Picker mit .inline-Style hat ein eigenartiges Problem: Man gibt ihm ein Label, und er zeigt stattdessen ein Chevron.
Das Problem ist bekannt und ärgerlich. PickerStyle.inline ignoriert das Label komplett und zeigt statt der Beschriftung nur einen einsamen Pfeil ohne jeden Kontext. Apple’s eigene Dokumentation schweigt dazu.
Picker(selection: $gender) {
Text(Gender.male.rawValue).tag(Gender.male)
Text(Gender.female.rawValue).tag(Gender.female)
Text(Gender.nonbinary.rawValue).tag(Gender.nonbinary)
} label: {
Text("Select your Gender")
}

Das Label wird deklariert, aber nie angezeigt. Der User sieht nur das Chevron und muss raten, wofür es steht.
Die Lösung: Menu als Wrapper
Wer ein Menü will, sollte tatsächlich auch ein Menü verwenden. Der Menu-View nimmt den Picker in seinen Content, und das Label lässt sich dabei frei gestalten.
Menu {
Picker("", selection: $gender) {
ForEach(Gender.allCases) { data in
Text(data.rawValue).tag(data)
}
}
.pickerStyle(.inline)
} label: {
HStack {
Text(gender.rawValue)
.font(.title3)
.frame(maxWidth: 200)
Image(systemName: "chevron.up.chevron.down")
.padding(.trailing)
}
.frame(maxWidth: .infinity)
.frame(height: 55)
.background(.white)
.cornerRadius(10)
.accentColor(.pink)
}
| Aspekt | Inline-Picker | Menu-Wrapper |
|---|---|---|
| Label sichtbar | Nein, nur ein Chevron | Ja, und frei gestaltbar |
| Klickbare Fläche | Nur das Chevron selbst | Der gesamte Button-Bereich |
| Styling | Stark eingeschränkt | Volle SwiftUI-Modifier |

Der gesamte Button-Bereich ist jetzt klickbar, das Label zeigt den aktuellen Wert, und das Menü öffnet sich sauber als Inline-Sheet.

Bonus: Generischer Enum-Picker
Bei drei Pickern im selben Formular wird der Code schnell redundant. Die Lösung ist ein generischer PickerView, der mit jedem konformen Enum funktioniert.
Das Protokoll
protocol PickerEnum: Hashable, CaseIterable {
var stringValue: String { get }
}
Die View
struct GenericPickerView<S: PickerEnum, Label: View, Content: View>: View {
let selection: Binding<S>
let label: Label
@ViewBuilder var content: (S) -> Content
var body: some View {
Picker(selection: selection, label: label) {
ForEach(S.allCases, id: \.self) {
content($0)
}
}
}
}
Verwendung
List {
GenericPickerView(
selection: $direction,
label: Text("Direction"),
content: { Text("\($0.stringValue.capitalized)").tag($0) }
)
GenericPickerView(
selection: $orientation,
label: Text("Orientation"),
content: { Text("\($0.stringValue.capitalized)").tag($0) }
)
}


Zwei Picker, null Redundanz. Neues Enum dazu? Einfach zu PickerEnum konformieren, fertig.
Die RandomAccessCollection-Falle
CaseIterable.AllCases konformiert nicht automatisch zu RandomAccessCollection, was beim Verwenden in ForEach zu einem Compiler-Fehler führt. Es gibt drei saubere Wege, das zu lösen:
| Variante | Bewertung |
|---|---|
ForEach(Array(S.allCases), id: \.self) | Funktioniert, erzeugt aber bei jedem Render einen Array-Cast |
where S.AllCases == Array<S> am Struct | Explizit und sicher |
where AllCases: RandomAccessCollection am Protokoll | Einmal definiert, überall gültig |
Die dritte Variante ist die sauberste, weil die Constraint direkt am Protokoll definiert wird und nicht an jeder View einzeln wiederholt werden muss.
Zwei Patterns, die in jedes SwiftUI-Projekt gehören: Menu statt Inline-Picker und Generics statt Copy-Paste.