Lernziel
Nach dieser Lektion kannst du eine App mit einer Tab-Leiste aufbauen: Tabs mit Tab anlegen, den gewählten Tab per Code wechseln, Badges anzeigen und jedem Tab seinen eigenen NavigationStack geben. Du weißt, wie Tabs auf dem iPad als Seitenleiste erscheinen und wie du eine Suche als eigenen Tab einbaust.
Das Problem: Mehrere gleichwertige Bereiche
Viele Apps haben mehrere gleichwertige Bereiche, zwischen denen der Nutzer ständig wechselt. Die Musik-App hat „Home“, „Neu“, „Radio“, „Mediathek“ und „Suche“. Die Uhr-App hat „Weltuhr“, „Wecker“, „Stoppuhr“ und „Timer“.
Diese Bereiche hängen nicht hierarchisch voneinander ab. Der Wecker ist keine Unterseite der Weltuhr. Mit einem NavigationStack lassen sie sich deshalb nicht sinnvoll abbilden. Dafür gibt es die Tab-Leiste am unteren Bildschirmrand: Jeder Bereich ist immer genau einen Tipp entfernt.
Eine einfache Tab-Leiste
struct ContentView: View {
var body: some View {
TabView {
Tab("Start", systemImage: "house") {
Text("Startseite")
}
Tab("Favoriten", systemImage: "star") {
Text("Deine Favoriten")
}
Tab("Einstellungen", systemImage: "gear") {
Text("Einstellungen")
}
}
}
}Erwartetes Ergebnis
Unten erscheint eine Leiste mit drei Symbolen und Beschriftungen. Der erste Tab ist ausgewählt und hervorgehoben. Ein Tipp auf einen anderen zeigt dessen Inhalt.
TabViewist der Container für alle Tabs.- Jeder
Tabbekommt einen Titel, ein SF Symbol und seinen Inhalt. - SwiftUI kümmert sich um alles andere: die Leiste, das Hervorheben, das Umschalten.
Tipp
In älterem Code siehst du Tabs oft so: Text("Start").tabItem { Label("Start", systemImage: "house") }. Das funktioniert weiterhin, aber der neuere Tab-Typ ist übersichtlicher und kann mehr, etwa die Suche als eigene Rolle und Seitenleisten auf dem iPad.
Apple empfiehlt drei bis fünf Tabs auf dem iPhone. Bei mehr wird es eng.
Jeder Tab mit eigenem NavigationStack
In echten Apps hat jeder Tab seine eigene Navigation. Wichtig ist dabei die Reihenfolge: Der NavigationStack gehört in den Tab, nicht um die TabView.
TabView {
Tab("Rezepte", systemImage: "fork.knife") {
NavigationStack {
RecipeListView()
}
}
Tab("Favoriten", systemImage: "heart") {
NavigationStack {
FavoritesView()
}
}
}So hat jeder Tab seinen eigenen Verlauf. Navigierst du im Rezepte-Tab drei Ebenen tief, wechselst zu den Favoriten und wieder zurück, bist du immer noch auf der dritten Ebene. Genau so verhalten sich Apples Apps.
Nicht so herum
NavigationStack {
TabView { ... } // ⚠️
}Dann teilen sich alle Tabs einen Stack, und beim Navigieren verschwindet die Tab-Leiste. Das ist fast nie gewünscht.
Den Tab per Code wechseln
Manchmal soll der Code den Tab wechseln: Nach dem Hinzufügen eines Favoriten springt die App zum Favoriten-Tab. Oder ein Deep Link öffnet einen bestimmten Tab (Lektion 3.22).
Dafür bekommt jeder Tab einen Wert, und die TabView eine Auswahl:
enum AppTab: Hashable {
case recipes, favorites, settings
}
struct ContentView: View {
@State private var selection: AppTab = .recipes
var body: some View {
TabView(selection: $selection) {
Tab("Rezepte", systemImage: "fork.knife", value: AppTab.recipes) {
NavigationStack {
RecipeListView()
}
}
Tab("Favoriten", systemImage: "heart", value: AppTab.favorites) {
NavigationStack {
FavoritesView()
}
}
Tab("Einstellungen", systemImage: "gear", value: AppTab.settings) {
SettingsView(onReset: {
selection = .recipes
})
}
}
}
}value:gibt jedem Tab eine Kennung, hier aus einem Enum.TabView(selection: $selection)verbindet die Auswahl mit einem State.selection = .recipeswechselt den Tab.
Das ist dasselbe Prinzip wie beim Picker (Lektion 2.13) und beim Navigationspfad (Lektion 3.20): Der Zustand bestimmt, was angezeigt wird.
Badges
Eine kleine rote Zahl am Tab zeigt, dass es dort etwas Neues gibt:
Tab("Favoriten", systemImage: "heart", value: AppTab.favorites) {
NavigationStack {
FavoritesView()
}
}
.badge(favorites.count).badge am Tab zeigt eine Zahl. Bei 0 verschwindet das Badge automatisch. Auch Text ist möglich: .badge("Neu").
Die Suche als eigener Tab
Viele Apps haben einen eigenen Such-Tab, oft ganz rechts und optisch etwas abgesetzt. Dafür gibt es eine besondere Rolle:
TabView {
Tab("Rezepte", systemImage: "fork.knife") {
NavigationStack { RecipeListView() }
}
Tab("Favoriten", systemImage: "heart") {
NavigationStack { FavoritesView() }
}
Tab(role: .search) {
NavigationStack {
SearchView()
}
}
}Tab(role: .search) braucht weder Titel noch Symbol: SwiftUI verwendet die Lupe und den passenden Titel automatisch. In aktuellen iOS-Versionen wird der Such-Tab außerdem vom Rest der Leiste abgesetzt dargestellt. Innerhalb der SearchView verwendest du wie gewohnt .searchable (Lektion 3.12).
Tabs auf dem iPad: Seitenleiste
Auf dem iPad kann eine Tab-Leiste zur Seitenleiste werden, mit Platz für viel mehr Einträge und Gruppen. Das schaltest du mit einem Stil ein:
TabView {
Tab("Start", systemImage: "house") { HomeView() }
TabSection("Sammlungen") {
Tab("Favoriten", systemImage: "heart") { FavoritesView() }
Tab("Zuletzt gekocht", systemImage: "clock") { RecentView() }
}
}
.tabViewStyle(.sidebarAdaptable).tabViewStyle(.sidebarAdaptable): Auf dem iPad kann der Nutzer zwischen Tab-Leiste oben und Seitenleiste umschalten. Auf dem iPhone bleibt es eine normale Tab-Leiste.TabSectiongruppiert Tabs unter einer Überschrift in der Seitenleiste.
Weil der Kurs auf das iPhone ausgerichtet ist, reicht es, wenn du weißt, dass es das gibt. Deine App sieht dann auf dem iPad ohne viel Mehraufwand gut aus.
Seitenweises Blättern mit TabView
TabView hat noch eine ganz andere Verwendung: Mit dem Stil .page wird es zu einem Blätter-Container, wie bei einer Einführung beim ersten Start einer App:
TabView {
OnboardingPage(title: "Willkommen", image: "hand.wave")
OnboardingPage(title: "Rezepte entdecken", image: "magnifyingglass")
OnboardingPage(title: "Favoriten speichern", image: "heart")
}
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))Der Nutzer wischt seitwärts durch die Seiten, und unten erscheinen Punkte. Mit .indexViewStyle(.page(backgroundDisplayMode: .always)) bekommen die Punkte einen Hintergrund, damit sie auch auf hellem Grund sichtbar sind. Dafür brauchst du keine Tabs, sondern einfach Views.
Tipp
Für Karussells innerhalb einer Seite ist die ScrollView mit .scrollTargetBehavior(.paging) aus Lektion 3.18 flexibler. Die TabView mit .page ist ideal für ganze Bildschirme wie ein Onboarding.
Typische Fehler
Tab-Leiste verschwindet beim Navigieren
Der NavigationStack liegt um die TabView statt in jedem Tab. Pack einen eigenen NavigationStack in jeden Tab.
Auswahl wechselt nicht
Die Werte der Tabs und der Typ der Auswahl passen nicht zusammen, etwa value: "recipes" bei einer Auswahl vom Typ AppTab. Nimm überall denselben Typ, am besten ein Enum.
Zu viele Tabs
Bei mehr als fünf Tabs zeigt iOS einen Tab „Mehr“, hinter dem der Rest versteckt ist. Das ist selten eine gute Nutzererfahrung. Überleg, ob sich Bereiche zusammenlegen lassen.
Zustand geht beim Tab-Wechsel verloren
Jeder Tab behält seinen Zustand, solange er in der TabView steht. Erzeugst du aber Tabs bedingt mit if, verschwindet ihr Zustand beim Ausblenden. Halte wichtige Daten in einem Objekt außerhalb der Tabs.
FAQ
Anfänger
Was bedeutet das überhaupt?Wann Tabs, wann Navigation? Tabs für gleichwertige, unabhängige Bereiche, zwischen denen man häufig wechselt. Navigation für eine Hierarchie: von der Übersicht ins Detail.
Kann ich ein eigenes Bild statt eines SF Symbols verwenden?
Ja, mit Tab("Titel", image: "MeinBild"). Das Bild sollte aber wie ein Symbol gestaltet sein: einfarbig und als Vorlage gerendert, damit es sich der Hervorhebung anpasst.
Fortgeschritten
Warum verhält es sich so?Wie springe ich beim erneuten Tipp auf den aktiven Tab zurück zum Anfang?
Apples Apps tun das automatisch, wenn der Tab einen NavigationStack enthält. Für eigenes Verhalten, etwa nach oben scrollen, kannst du eine Auswahl-Bindung bauen, deren set erkennt, dass derselbe Tab erneut gewählt wurde, und dann den Pfad des Tabs leert.
Wie gestalte ich die Tab-Leiste farblich?
Mit .tint(.orange) an der TabView änderst du die Hervorhebungsfarbe. Für den Hintergrund gibt es .toolbarBackground(..., for: .tabBar). Halte dich aber möglichst an das Standardaussehen: Nutzer erkennen es sofort, und es passt sich neuen iOS-Designs automatisch an.
Profi
Was passiert unter der Haube?Werden alle Tabs beim Start gebaut?
Nein. SwiftUI erzeugt den Inhalt eines Tabs, wenn er das erste Mal ausgewählt wird, und behält ihn danach. Arbeit, die beim ersten Anzeigen passieren soll, gehört deshalb in .task des Tab-Inhalts, nicht in den Initializer.
Kann der Nutzer die Tabs anpassen?
Mit .sidebarAdaptable und einer TabViewCustomization kann der Nutzer auf dem iPad Tabs ein- und ausblenden und umsortieren. Die Anpassung lässt sich mit @AppStorage speichern. Jeder anpassbare Tab braucht dafür eine stabile .customizationID.
Übungen
Übung 1: Drei Tabs
Baue eine TabView mit drei Tabs: „Heute“, „Verlauf“, „Profil“. Jeder Tab hat einen eigenen NavigationStack mit einem Titel und einem passenden Text.
Übung 2: Zähler mit Badge
Erweitere Übung 1: Im Tab „Heute“ gibt es einen Button „Aufgabe erledigt“, der einen Zähler erhöht. Der Tab „Verlauf“ zeigt den Zähler als Badge. Ein Button „Verlauf ansehen“ im ersten Tab wechselt per Code zum zweiten Tab.
Lösung Übung 2Erst selbst probieren!
enum HomeTab: Hashable { case today, history, profile }
struct HomeTabs: View {
@State private var selection: HomeTab = .today
@State private var done = 0
var body: some View {
TabView(selection: $selection) {
Tab("Heute", systemImage: "sun.max", value: HomeTab.today) {
NavigationStack {
VStack(spacing: 16) {
Button("Aufgabe erledigt") { done += 1 }
.buttonStyle(.borderedProminent)
Button("Verlauf ansehen") { selection = .history }
}
.navigationTitle("Heute")
}
}
Tab("Verlauf", systemImage: "clock", value: HomeTab.history) {
NavigationStack {
Text("\(done) erledigt")
.navigationTitle("Verlauf")
}
}
.badge(done)
Tab("Profil", systemImage: "person", value: HomeTab.profile) {
NavigationStack {
Text("Profil").navigationTitle("Profil")
}
}
}
}
}Übung 3: Onboarding vor den Tabs
Zeig beim ersten Start ein dreiseitiges Onboarding mit TabView und .page-Stil. Auf der letzten Seite führt ein Button „Los geht's“ zur eigentlichen App mit Tabs. Merk dir mit @AppStorage, dass das Onboarding schon gesehen wurde (Lektion 3.13).
Zusammenfassung
TabViewmitTabbaut eine Tab-Leiste für gleichwertige Bereiche, ideal drei bis fünf.- Jeder Tab bekommt seinen eigenen
NavigationStack, nicht umgekehrt. TabView(selection:)undTab(value:)machen den aktiven Tab zum Zustand, den Code ändern kann..badgezeigt Zahlen oder Text am Tab.Tab(role: .search)für einen eigenen Such-Tab..tabViewStyle(.sidebarAdaptable)undTabSectionfür eine Seitenleiste auf dem iPad..tabViewStyle(.page)macht aus derTabVieweinen Blätter-Container für Onboardings.
Spickzettel
enum AppTab: Hashable { case home, favorites }
@State private var selection: AppTab = .home
TabView(selection: $selection) {
Tab("Start", systemImage: "house", value: AppTab.home) {
NavigationStack { HomeView() }
}
Tab("Favoriten", systemImage: "heart", value: AppTab.favorites) {
NavigationStack { FavoritesView() }
}
.badge(count)
Tab(role: .search) {
NavigationStack { SearchView() }
}
}
selection = .favorites // Tab per Code wechseln
TabView { PageA(); PageB() }
.tabViewStyle(.page) // Blättern