Lernziel
Nach dieser Lektion kannst du Buttons, Menüs und andere Elemente mit .toolbar in die Navigationsleiste und andere Bereiche setzen, die passenden Platzierungen wählen, Einträge gruppieren und die Leiste selbst gestalten.
Das Problem: Wohin mit den Aktionen?
Deine Liste aus der letzten Lektion braucht einen Knopf zum Hinzufügen eines Eintrags. Und vielleicht einen zum Sortieren. Einen zum Bearbeiten. Packst du sie alle in die Liste, verdrängen sie den eigentlichen Inhalt.
iOS hat für solche Aktionen feste Orte: oben rechts in der Navigationsleiste das Plus der Notizen-App, unten die Werkzeugleiste in Safari. Nutzer erwarten Aktionen genau dort. Mit .toolbar platzierst du Elemente an diesen Stellen.
Die erste Toolbar
struct NotesList: View {
@State private var notes = ["Einkaufsliste", "Ideen"]
var body: some View {
NavigationStack {
List(notes, id: \.self) { note in
Text(note)
}
.navigationTitle("Notizen")
.toolbar {
Button("Neu", systemImage: "plus") {
notes.append("Neue Notiz \(notes.count + 1)")
}
}
}
}
}Erwartetes Ergebnis
Oben rechts in der Navigationsleiste erscheint ein Plus-Symbol. Jeder Tipp fügt der Liste eine Notiz hinzu.
.toolbar { ... }steht wie.navigationTitlean der View imNavigationStack.- Ein Button mit
systemImagezeigt in der Leiste nur das Symbol. Der Titel „Neu“ wird trotzdem verwendet: VoiceOver liest ihn vor, und bei langem Drücken erscheint er als Hinweis. Lass ihn deshalb nie weg.
Platzierungen mit ToolbarItem
Ohne Angabe wählt SwiftUI selbst, wo ein Element landet, auf dem iPhone meist oben rechts. Mit ToolbarItem bestimmst du die Position:
.toolbar {
ToolbarItem(placement: .topBarLeading) {
EditButton()
}
ToolbarItem(placement: .topBarTrailing) {
Button("Neu", systemImage: "plus") { addNote() }
}
ToolbarItem(placement: .bottomBar) {
Text("\(notes.count) Notizen")
.font(.footnote)
}
}| Platzierung | Ort |
|---|---|
.topBarLeading | oben links |
.topBarTrailing | oben rechts |
.principal | oben in der Mitte, anstelle des Titels |
.bottomBar | Werkzeugleiste am unteren Rand |
.keyboard | Leiste über der Tastatur (Lektion 2.12) |
.primaryAction | wichtigste Aktion, auf dem iPhone oben rechts |
.confirmationAction | „Fertig“ oder „Speichern“ in Dialogen |
.cancellationAction | „Abbrechen“ in Dialogen |
Bedeutung statt Ort
Die letzten drei Platzierungen beschreiben die Bedeutung statt einer festen Position. SwiftUI setzt sie dorthin, wo sie auf der jeweiligen Plattform hingehören. In Sheets (nächste Lektion) sind .cancellationAction und .confirmationAction die richtige Wahl: „Abbrechen“ landet links, „Speichern“ rechts, wie in allen Systemapps.
Mehrere Elemente gruppieren
Mehrere Elemente an derselben Stelle fasst du mit ToolbarItemGroup zusammen:
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button("Teilen", systemImage: "square.and.arrow.up") { }
Spacer()
Button("Favorit", systemImage: "star") { }
Spacer()
Button("Löschen", systemImage: "trash") { }
}
}Die Spacer verteilen die Buttons gleichmäßig über die Leiste, genau wie in einem HStack (Lektion 2.7).
Menüs in der Toolbar
Für mehrere verwandte Aktionen bietet sich ein Menü an. Es spart Platz und hält die Leiste ruhig:
.toolbar {
Menu("Sortieren", systemImage: "arrow.up.arrow.down") {
Button("Nach Name") { sortByName() }
Button("Nach Datum") { sortByDate() }
Divider()
Button("Zurücksetzen", role: .destructive) { reset() }
}
}Ein Tipp auf das Symbol öffnet ein Menü mit den Optionen. Divider trennt Gruppen, und Buttons mit role: .destructive werden rot.
Auch ein Picker funktioniert in einem Menü. Dann bekommt die gewählte Option automatisch ein Häkchen:
@State private var sortOrder = "Name"
.toolbar {
Menu("Sortieren", systemImage: "arrow.up.arrow.down") {
Picker("Sortierung", selection: $sortOrder) {
Text("Name").tag("Name")
Text("Datum").tag("Datum")
}
}
}Ein Titel in der Mitte
Mit .principal ersetzt du den Titel durch eine eigene View, zum Beispiel einen segmentierten Picker:
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Ansicht", selection: $view) {
Text("Liste").tag("Liste")
Text("Raster").tag("Raster")
}
.pickerStyle(.segmented)
.frame(width: 200)
}
}Die Leiste gestalten
Einige Modifier verändern die Navigationsleiste selbst:
.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(.indigo, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar).toolbarBackground(.visible, ...)zeigt den Hintergrund der Leiste immer, nicht erst beim Scrollen..toolbarBackground(.indigo, ...)färbt ihn..toolbarColorScheme(.dark, ...)sorgt für helle Schrift auf dunklem Grund.
Und ganz ausblenden kannst du die Leiste mit .toolbar(.hidden, for: .navigationBar).
Achtung
Sei mit farbigen Leisten vorsichtig. Das aktuelle iOS-Design setzt auf zurückhaltende, durchscheinende Leisten, die sich dem Inhalt unterordnen. Eine knallbunte Leiste wirkt schnell altmodisch. Mehr zum aktuellen Apple-Design in Lektion 3.30.
Toolbars in Unterseiten
Jede View im Navigationsstapel kann ihre eigene Toolbar haben. Navigiert der Nutzer zur Detailansicht, verschwinden die Einträge der Liste, und die der Detailansicht erscheinen:
struct NoteDetail: View {
let note: String
@State private var isFavorite = false
var body: some View {
Text(note)
.navigationTitle(note)
.toolbar {
Button("Favorit", systemImage: isFavorite ? "star.fill" : "star") {
isFavorite.toggle()
}
}
}
}Das Symbol wechselt abhängig vom Zustand. Auch das ist einfach eine Beschreibung (Lektion 2.1).
Typische Fehler
Toolbar erscheint nicht
Die View steht nicht in einem NavigationStack, oder .toolbar hängt am NavigationStack statt an der View darin. Wie beim Titel gilt: Modifier an den Inhalt.
Button ohne Titel
Button { addNote() } label: {
Image(systemName: "plus")
}Funktioniert, aber VoiceOver liest dann nur „plus, Taste“ vor, oder gar nichts Sinnvolles. Nimm Button("Neu", systemImage: "plus") oder ein Label. Die Leiste zeigt trotzdem nur das Symbol.
Zu viele Elemente
Mehr als zwei, drei Elemente pro Seite der Navigationsleiste werden eng, auf kleinen iPhones schneidet iOS sie ab. Fass Nebenaktionen in einem Menu zusammen.
bottomBar erscheint nicht
Die untere Leiste braucht ebenfalls einen NavigationStack. Bei Apps mit TabView (Lektion 3.21) konkurriert sie außerdem mit der Tab-Leiste.
FAQ
Anfänger
Was bedeutet das überhaupt?Was ist der Unterschied zwischen .toolbar und einem Button im Inhalt?
Ein Button in der Toolbar ist immer sichtbar, auch beim Scrollen, und steht dort, wo Nutzer Aktionen erwarten. Ein Button im Inhalt scrollt mit. Für Aktionen, die den ganzen Bildschirm betreffen, wie „Neu“ oder „Bearbeiten“, ist die Toolbar der richtige Ort.
Kann ich eigene Bilder in der Toolbar verwenden?
Ja, im label eines Buttons darf jedes Bild stehen. SF Symbols passen sich aber automatisch der Leiste an, deshalb sind sie meist die bessere Wahl.
Fortgeschritten
Warum verhält es sich so?Was ist das Titelmenü?
Mit .toolbarTitleMenu { ... } bekommt der Titel selbst einen kleinen Pfeil und öffnet beim Antippen ein Menü, etwa mit „Umbenennen“ oder „Duplizieren“. Das kennst du aus der Dateien-App.
Kann ich die Toolbar aus einer Unter-View befüllen?
Ja. Jede View im Stapel darf .toolbar verwenden, auch tief verschachtelte. SwiftUI sammelt alle Einträge der aktuell sichtbaren Views ein und setzt sie zusammen.
Profi
Was passiert unter der Haube?Wie kommen die Einträge von der View in die Leiste?
Toolbar-Inhalte sind sogenannte Preferences: Informationen, die von Kind-Views nach oben gereicht werden, im Gegensatz zur Umgebung, die nach unten geht. Der NavigationStack liest die Preferences der obersten View aus und baut daraus die Leiste. Mit eigenen PreferenceKeys kannst du dasselbe Prinzip für eigene Zwecke nutzen.
Warum gibt es ToolbarItem und ToolbarContent?
.toolbar erwartet einen eigenen Ergebnistyp, ToolbarContent, nicht beliebige Views. Einzelne Views werden automatisch in ToolbarItems verpackt. Willst du Toolbar-Inhalte in eine eigene Struct auslagern, lässt du sie das Protokoll ToolbarContent erfüllen, ganz ähnlich wie View.
Übungen
Übung 1: Notizen mit Toolbar
Baue eine Notizen-Liste mit einem „Neu“-Button oben rechts und einem EditButton oben links. Die Notizen sollen sich löschen lassen.
Lösung Übung 1Erst selbst probieren!
struct Note: Identifiable {
let id = UUID()
var title: String
}
struct NotesView: View {
@State private var notes = [Note(title: "Einkaufsliste")]
var body: some View {
NavigationStack {
List {
ForEach(notes) { note in
Text(note.title)
}
.onDelete { notes.remove(atOffsets: $0) }
}
.navigationTitle("Notizen")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
EditButton()
}
ToolbarItem(placement: .topBarTrailing) {
Button("Neu", systemImage: "plus") {
notes.append(Note(title: "Notiz \(notes.count + 1)"))
}
}
}
}
}
}Übung 2: Sortiermenü
Erweitere Übung 1: Jede Notiz bekommt ein Erstellungsdatum. Ein Sortiermenü in der Toolbar erlaubt, nach Titel oder nach Datum zu sortieren. Die Auswahl wird mit einem Häkchen angezeigt. Verwende ein Enum für die Sortierung und eine berechnete Eigenschaft sortedNotes.
Übung 3: Werkzeugleiste unten
Baue eine Detailansicht für eine Notiz mit einer unteren Werkzeugleiste: links ein Favoriten-Button, dessen Symbol sich ändert, in der Mitte ein Text mit der Anzahl der Zeichen und rechts ein Teilen-Button. Für den echten Teilen-Dialog gibt es übrigens ShareLink(item: note.title). Probier ihn aus.
Zusammenfassung
.toolbar { ... }setzt Elemente in die Navigationsleiste. Es steht an der View imNavigationStack.ToolbarItem(placement:)bestimmt die Position:.topBarLeading,.topBarTrailing,.principal,.bottomBar,.keyboard.- Bedeutungsbasierte Platzierungen wie
.confirmationActionund.cancellationActionfür Dialoge. ToolbarItemGroupfasst mehrere Elemente zusammen,Menuspart Platz.- Buttons in der Toolbar immer mit Titel, auch wenn nur das Symbol sichtbar ist.
- Jede View im Stapel hat ihre eigene Toolbar.
Spickzettel
.toolbar {
ToolbarItem(placement: .topBarLeading) { EditButton() }
ToolbarItem(placement: .topBarTrailing) {
Button("Neu", systemImage: "plus") { add() }
}
ToolbarItemGroup(placement: .bottomBar) {
Button("Teilen", systemImage: "square.and.arrow.up") { }
Spacer()
Button("Löschen", systemImage: "trash") { }
}
}
Menu("Mehr", systemImage: "ellipsis.circle") {
Button("Umbenennen") { }
Button("Löschen", role: .destructive) { }
}