Lektion 2.7 · Stufe 2 – SwiftUI-Einstieg

Stacks

ca. 15 Minuten Lesezeit

Lernziel

Nach dieser Lektion kannst du Views mit VStack, HStack und ZStack anordnen, Abstände und Ausrichtung in Stacks steuern, mit Spacer und Divider Platz verteilen und Stacks ineinander verschachteln, um beliebige Layouts zu bauen.

Das Problem: Mehr als eine View

Bisher hast du meistens eine einzelne View gestaltet. Echte Bildschirme bestehen aber aus vielen Elementen: Ein Profilbild neben einem Namen, darunter untereinander mehrere Infos, und auf dem Bild übereinander ein kleines Abzeichen.

In SwiftUI gibt es dafür drei Grundbausteine, die Stacks (Stapel):

StackRichtungBild
VStackvertikal, untereinanderBücher auf einem Stapel
HStackhorizontal, nebeneinanderBücher in einem Regal
ZStackübereinander, in die TiefeBlätter auf einem Tisch

Mit diesen dreien und etwas Verschachtelung lässt sich erstaunlich viel bauen.

VStack: Untereinander

Swift
VStack {
    Text("Erste Zeile")
    Text("Zweite Zeile")
    Text("Dritte Zeile")
}

Die Views werden von oben nach unten angeordnet, in der Reihenfolge, in der sie im Code stehen. Den VStack kennst du seit Lektion 0.4.

Abstand und Ausrichtung

Ein Stack hat zwei wichtige Parameter:

Swift
VStack(alignment: .leading, spacing: 12) {
    Text("Anna Berger")
        .font(.headline)
    Text("iOS-Entwicklerin")
        .foregroundStyle(.secondary)
    Text("Wien")
        .font(.footnote)
}
  • spacing: der Abstand zwischen den Elementen in Punkten. Ohne Angabe wählt SwiftUI einen passenden Standardabstand.
  • alignment: wie die Elemente quer zur Stapelrichtung ausgerichtet werden. Beim VStack also horizontal: .leading (links), .center (Standard) oder .trailing (rechts).

Erwartetes Ergebnis

Drei Zeilen untereinander, alle links ausgerichtet, mit je 12 Punkten Abstand.

Achtung

alignment richtet die Elemente zueinander aus, nicht den ganzen Stack auf dem Bildschirm. Der VStack selbst ist nur so breit wie sein breitestes Element und steht weiterhin in der Mitte. Wie du ihn an den Rand schiebst, lernst du in der nächsten Lektion.

HStack: Nebeneinander

Swift
HStack(spacing: 16) {
    Image(systemName: "person.circle.fill")
        .font(.largeTitle)
    Text("Anna Berger")
}

Hier funktioniert alignment vertikal: .top, .center (Standard), .bottom und, besonders praktisch, .firstTextBaseline:

Swift
HStack(alignment: .firstTextBaseline) {
    Text("42")
        .font(.largeTitle)
    Text("Punkte")
        .font(.caption)
}

Mit .firstTextBaseline stehen beide Texte auf derselben Grundlinie, wie Wörter in einem Satz, obwohl sie unterschiedlich groß sind. Mit .center wäre „Punkte“ in der Mitte der großen Zahl, was meist unruhig aussieht.

ZStack: Übereinander

Swift
ZStack {
    Circle()
        .fill(.blue)
        .frame(width: 100, height: 100)
    Text("AB")
        .font(.title)
        .foregroundStyle(.white)
}

Im ZStack liegt das zuerst geschriebene Element ganz hinten, jedes weitere davor. Hier liegt also der Text auf dem Kreis.

Auch der ZStack hat ein alignment, und zwar in beide Richtungen: .topLeading, .top, .topTrailing, .leading, .center, .trailing, .bottomLeading, .bottom, .bottomTrailing.

Swift
ZStack(alignment: .bottomTrailing) {
    Image(systemName: "envelope.fill")
        .font(.system(size: 60))
    Text("3")
        .font(.caption)
        .bold()
        .foregroundStyle(.white)
        .padding(6)
        .background(.red)
        .clipShape(.circle)
}

Erwartetes Ergebnis

Ein Briefumschlag-Symbol mit einem roten Kreis mit der Zahl 3 in der rechten unteren Ecke.

ZStack oder background / overlay?

Oft ist .background { ... } oder .overlay { ... } die bessere Wahl als ein ZStack. Der Unterschied: Beim ZStack bestimmt das größte Element die Größe. Bei .overlay und .background bestimmt die ursprüngliche View die Größe, und das hinzugefügte Element passt sich an. Für ein Abzeichen auf einem Symbol ist deshalb .overlay(alignment: .bottomTrailing) { ... } meist eleganter.

Spacer: Platz verteilen

Ein Spacer ist eine unsichtbare View, die so viel Platz wie möglich einnimmt. Damit schiebst du Elemente auseinander:

Swift
HStack {
    Text("Links")
    Spacer()
    Text("Rechts")
}
.padding()

Der Spacer nimmt den ganzen freien Platz zwischen den beiden Texten ein, und sie landen am linken und rechten Rand.

Mit Spacern kannst du Elemente gezielt positionieren:

Swift
HStack {
    Spacer()
    Text("Ganz rechts")
}

VStack {
    Text("Oben")
    Spacer()
    Text("Unten")
}

Mehrere Spacer teilen sich den Platz gleichmäßig:

Swift
HStack {
    Spacer()
    Text("A")
    Spacer()
    Text("B")
    Spacer()
}

Spacer(minLength: 20) garantiert dabei einen Mindestabstand.

Divider: Trennlinien

Swift
VStack {
    Text("Abschnitt 1")
    Divider()
    Text("Abschnitt 2")
}

Ein Divider zeichnet eine feine Linie, im VStack waagrecht und im HStack senkrecht.

Stacks verschachteln

Die eigentliche Stärke entsteht, wenn du Stacks ineinander steckst. Eine typische Kontaktzeile:

Swift
HStack(spacing: 12) {
    Image(systemName: "person.circle.fill")
        .font(.system(size: 44))
        .foregroundStyle(.blue)

    VStack(alignment: .leading, spacing: 2) {
        Text("Anna Berger")
            .font(.headline)
        Text("Zuletzt online vor 5 Min.")
            .font(.subheadline)
            .foregroundStyle(.secondary)
    }

    Spacer()

    Image(systemName: "phone.fill")
        .foregroundStyle(.green)
}
.padding()

Erwartetes Ergebnis

Links ein blaues Personen-Symbol, daneben Name und Status untereinander, ganz rechts ein grünes Telefon-Symbol.

Screenshot folgt: Kontaktzeile mit Symbol, Name, Status und Telefon-Symbol

Lies es von außen nach innen: Eine waagrechte Reihe aus Bild, einem senkrechten Block mit zwei Texten, Leerraum und einem Symbol.

Layouts planen

Bevor du ein Layout baust, zeichne es grob auf und zieh Kästen darum: Was steht nebeneinander? Was untereinander? Was übereinander? Jeder Kasten wird ein Stack. Nach ein paar Layouts machst du das automatisch im Kopf.

Wie Stacks ihren Platz verteilen

Dieser Teil ist am Anfang etwas abstrakt, erklärt aber viele Überraschungen. SwiftUI verteilt Platz in drei Schritten:

  1. Die Eltern-View schlägt ihrem Kind eine Größe vor: „Du hast 390 × 800 Punkte.“
  2. Das Kind entscheidet selbst, wie groß es sein will: Ein Text braucht nur so viel Platz wie seine Buchstaben. Ein Spacer und eine Farbe nehmen alles, was sie bekommen.
  3. Die Eltern-View platziert das Kind entsprechend ihrer Ausrichtung.

Ein Stack verteilt den Platz unter seinen Kindern. Wer flexibel ist, wie Spacer oder Color, bekommt den Rest, nachdem die anderen versorgt sind.

Das erklärt zum Beispiel:

Swift
VStack {
    Color.blue
    Text("Hallo")
}

Die blaue Fläche füllt fast den ganzen Bildschirm, denn eine Farbe nimmt so viel Platz, wie sie bekommt. Der Text nimmt nur, was er braucht.

Typische Fehler

Ausrichtung ohne Wirkung

Swift
VStack(alignment: .leading) {
    Text("Kurz")
}

Der Text steht trotzdem in der Mitte des Bildschirms. alignment richtet nur innerhalb des Stacks aus, und der Stack ist hier nur so breit wie der Text. Lösung in der nächsten Lektion: .frame(maxWidth: .infinity, alignment: .leading).

Spacer im falschen Stack

Swift
VStack {
    Text("A")
    Spacer()
    Text("B")
}

Wolltest du A und B nebeneinander mit Abstand? Dann gehört der Spacer in einen HStack. Ein Spacer drückt immer in Richtung seines Stacks.

Zu viele Elemente

Ein Stack kann theoretisch beliebig viele Views enthalten. Bei sehr vielen Elementen, etwa 1.000 Zeilen, solltest du aber eine List (Lektion 2.15) oder einen LazyVStack (Lektion 3.18) verwenden, die nur den sichtbaren Teil erzeugen.

Unerwartete Reihenfolge im ZStack

Ein Element verschwindet? Im ZStack liegt es vielleicht hinter einem größeren Element. Denk daran: Was zuerst geschrieben wird, liegt hinten.

FAQ

Anfänger

Was bedeutet das überhaupt?

Wie tief darf ich Stacks verschachteln? So tief, wie es nötig ist. Wird der Code aber unübersichtlich, ist das ein Zeichen, Teile in eigene Views auszulagern (Lektion 2.21).

Was ist der Standardabstand, wenn ich kein spacing angebe? SwiftUI wählt einen Abstand, der zu den enthaltenen Views passt, typischerweise etwa 8 Punkte. Wenn dir das Ergebnis wichtig ist, gib den Abstand ausdrücklich an. Mit spacing: 0 liegen die Elemente direkt aneinander.

Was ist ein „Punkt“? SwiftUI misst nicht in Pixeln, sondern in Punkten. Ein Punkt entspricht je nach Bildschirm zwei oder drei Pixeln. So sieht dein Layout auf allen iPhones gleich groß aus, egal wie scharf das Display ist.

Fortgeschritten

Warum verhält es sich so?

Warum schrumpft mein Text, statt umzubrechen? Wenn ein HStack zu wenig Platz hat, muss er entscheiden, welches Element nachgibt. Mit .layoutPriority(1) gibst du einer View Vorrang. Sie bekommt dann zuerst den Platz, den sie braucht.

Was ist Grid? Ein Layout-Container für tabellenartige Anordnungen, bei denen Spalten über mehrere Zeilen hinweg ausgerichtet sein sollen. Für einfache Fälle reichen Stacks. Grid und LazyVGrid lernst du in Lektion 3.17.

Profi

Was passiert unter der Haube?

Wie entscheidet ein Stack genau, wer wie viel Platz bekommt? Der Stack fragt jedes Kind, wie flexibel es ist, sortiert die Kinder nach ihrer Flexibilität und verteilt den verfügbaren Platz zuerst an die unflexibelsten. Was übrig bleibt, teilen sich die flexiblen Kinder. Die layoutPriority hat dabei Vorrang vor der Flexibilität.

Wie würde ich einen eigenen Stack bauen? Mit dem Layout-Protokoll kannst du eigene Container mit beliebiger Platzierungslogik schreiben, etwa einen Stack, der Elemente im Kreis anordnet. Das ist Thema von Lektion 4.6.

Übungen

Anfänger

Übung 1: Wetter-Zeile

Baue eine Zeile mit einem Wochentag links, einem Wettersymbol in der Mitte und der Temperatur rechts. Tipp: zwei Spacer.

Lösung Übung 1Erst selbst probieren!
Swift
HStack {
    Text("Montag")
    Spacer()
    Image(systemName: "cloud.sun.fill")
        .symbolRenderingMode(.multicolor)
    Spacer()
    Text("18 °C")
}
.padding()

Achtung: Das Symbol steht nur dann genau in der Mitte, wenn die Texte links und rechts gleich breit sind. Für exakte Spalten eignet sich später Grid besser.

Fortgeschritten

Übung 2: Statistik-Kacheln

Baue drei Kacheln nebeneinander. Jede zeigt groß eine Zahl und darunter klein eine Beschreibung, etwa „128 Beiträge“, „4.200 Follower“ und „312 folge ich“. Jede Kachel hat einen hellgrauen Hintergrund mit abgerundeten Ecken.

Lösung Übung 2Erst selbst probieren!
Swift
HStack(spacing: 12) {
    VStack {
        Text("128").font(.title2).bold()
        Text("Beiträge").font(.caption).foregroundStyle(.secondary)
    }
    .padding()
    .background(Color(.secondarySystemBackground))
    .clipShape(.rect(cornerRadius: 12))

    VStack {
        Text("4.200").font(.title2).bold()
        Text("Follower").font(.caption).foregroundStyle(.secondary)
    }
    .padding()
    .background(Color(.secondarySystemBackground))
    .clipShape(.rect(cornerRadius: 12))

    VStack {
        Text("312").font(.title2).bold()
        Text("folge ich").font(.caption).foregroundStyle(.secondary)
    }
    .padding()
    .background(Color(.secondarySystemBackground))
    .clipShape(.rect(cornerRadius: 12))
}

Fällt dir die Wiederholung auf? In Lektion 2.21 lernst du, daraus eine eigene StatTile-View zu machen. Gleich breit werden die Kacheln mit .frame(maxWidth: .infinity) aus der nächsten Lektion.

Profi

Übung 3: App-Symbol mit Badge

Baue ein App-Symbol: ein abgerundetes Quadrat (60 × 60) mit Farbverlauf und einem weißen SF Symbol darin. Oben rechts soll ein rotes Abzeichen mit einer Zahl sitzen, das leicht über den Rand hinausragt. Löse es einmal mit ZStack und einmal mit .overlay. Welche Variante gefällt dir besser?

Lösung Übung 3Erst selbst probieren!
Swift
RoundedRectangle(cornerRadius: 14)
    .fill(.blue.gradient)
    .frame(width: 60, height: 60)
    .overlay {
        Image(systemName: "envelope.fill")
            .font(.title)
            .foregroundStyle(.white)
    }
    .overlay(alignment: .topTrailing) {
        Text("5")
            .font(.caption)
            .bold()
            .foregroundStyle(.white)
            .padding(6)
            .background(.red)
            .clipShape(.circle)
            .offset(x: 8, y: -8)
    }

.offset(x:y:) verschiebt das Abzeichen ein Stück nach außen. Mit .overlay bleibt das Symbol genau 60 × 60 groß. Beim ZStack würde das hinausragende Abzeichen die Gesamtgröße beeinflussen.

Zusammenfassung

  • VStack ordnet untereinander, HStack nebeneinander, ZStack übereinander an.
  • spacing steuert den Abstand zwischen den Elementen, alignment die Ausrichtung quer zur Stapelrichtung.
  • Im ZStack liegt das zuerst geschriebene Element hinten.
  • Spacer nimmt allen freien Platz und schiebt Elemente auseinander. Divider zeichnet Trennlinien.
  • Komplexe Layouts entstehen durch Verschachteln.
  • Eltern schlagen eine Größe vor, Kinder entscheiden selbst, Eltern platzieren.

Spickzettel

Swift
VStack(alignment: .leading, spacing: 8) { ... }
HStack(alignment: .firstTextBaseline, spacing: 12) { ... }
ZStack(alignment: .bottomTrailing) { ... }
Spacer()   Spacer(minLength: 20)   Divider()

HStack {
    Image(...)
    VStack(alignment: .leading) { Text(...); Text(...) }
    Spacer()
    Image(...)
}
.overlay(alignment: .topTrailing) { Badge() }

Lektion geschafft?

Markiere sie als erledigt, damit du deinen Fortschritt siehst.