Lektion 3.21 · Stufe 3 – Fortgeschritten

Tabs

ca. 15 Minuten Lesezeit

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

Swift
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.

Screenshot folgt: Tab-Leiste mit Start, Favoriten und Einstellungen
  • TabView ist der Container für alle Tabs.
  • Jeder Tab bekommt 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.

Swift
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

Swift
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:

Swift
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 = .recipes wechselt 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:

Swift
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:

Swift
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:

Swift
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.
  • TabSection gruppiert 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:

Swift
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

Anfänger

Ü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.

Fortgeschritten

Ü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!
Swift
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")
                }
            }
        }
    }
}
Profi

Ü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

  • TabView mit Tab baut eine Tab-Leiste für gleichwertige Bereiche, ideal drei bis fünf.
  • Jeder Tab bekommt seinen eigenen NavigationStack, nicht umgekehrt.
  • TabView(selection:) und Tab(value:) machen den aktiven Tab zum Zustand, den Code ändern kann.
  • .badge zeigt Zahlen oder Text am Tab.
  • Tab(role: .search) für einen eigenen Such-Tab.
  • .tabViewStyle(.sidebarAdaptable) und TabSection für eine Seitenleiste auf dem iPad.
  • .tabViewStyle(.page) macht aus der TabView einen Blätter-Container für Onboardings.

Spickzettel

Swift
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

Lektion geschafft?

Markiere sie als erledigt, damit du deinen Fortschritt siehst.