Lektion 2.8 · Stufe 2 – SwiftUI-Einstieg

Abstände, Rahmen und Ausrichtung

ca. 15 Minuten Lesezeit

Lernziel

Nach dieser Lektion kannst du Abstände mit padding gezielt setzen, Views mit frame eine feste, minimale oder maximale Größe geben, sie mit alignment an jede Stelle eines Bereichs schieben und mit dem sicheren Bereich des Bildschirms umgehen.

Das Problem: Fast richtig ist nicht richtig

Mit Stacks aus der letzten Lektion kannst du Views anordnen. Aber dann kommen die Feinheiten:

  • Der Titel soll links am Bildschirmrand stehen, nicht in der Mitte.
  • Ein Button soll die ganze Breite einnehmen.
  • Zwischen Inhalt und Rand soll links und rechts mehr Luft sein als oben und unten.
  • Ein Hintergrundbild soll bis ganz an den Rand gehen, auch hinter die Statusleiste.

Für all das gibt es drei Werkzeuge: padding, frame und alignment.

padding: Luft schaffen

Du kennst .padding() schon. Ohne Angaben fügt es auf allen vier Seiten einen Standardabstand hinzu. Du kannst es aber genau steuern:

Swift
Text("Alle Seiten 20").padding(20)
Text("Nur oben").padding(.top)
Text("Links und rechts").padding(.horizontal, 24)
Text("Oben und unten").padding(.vertical, 8)
Text("Unten und rechts").padding([.bottom, .trailing], 12)
AngabeSeiten
.top, .bottom, .leading, .trailingeine Seite
.horizontallinks und rechts
.verticaloben und unten
.allalle (Standard)
[.top, .leading]mehrere als Array

Mehrere padding-Modifier hintereinander addieren sich:

Swift
Text("Button")
    .padding(.horizontal, 24)
    .padding(.vertical, 12)

Tipp

Bleib bei einer kleinen Auswahl an Abständen, zum Beispiel 4, 8, 12, 16, 24 und 32. Ein Layout mit einheitlichen Abständen wirkt sofort ruhiger und professioneller als eines, in dem überall andere Werte stehen. In Lektion 3.29 legst du dafür eigene Konstanten an.

frame: Größe festlegen

Feste Größe

Swift
Text("Fest")
    .frame(width: 200, height: 60)
    .background(.yellow)

Der gelbe Bereich ist genau 200 × 60 Punkte groß, und der Text steht in seiner Mitte. Du kannst auch nur eine der beiden Angaben machen, etwa .frame(height: 60).

Wichtig zu verstehen: .frame ändert nicht die Größe des Textes. Es legt einen Rahmen um ihn, und der Text wird darin platziert. Deshalb kommt .background hier nach .frame (Lektion 2.4).

Flexible Größe mit maxWidth

Eine der wichtigsten Zeilen in SwiftUI überhaupt:

Swift
Text("Volle Breite")
    .frame(maxWidth: .infinity)
    .padding()
    .background(.blue)
    .foregroundStyle(.white)

maxWidth: .infinity heißt: „Werde so breit, wie du darfst.“ Der Rahmen dehnt sich über die ganze verfügbare Breite aus. So baust du zum Beispiel Buttons über die volle Breite.

Ebenso gibt es maxHeight: .infinity und die Gegenstücke minWidth und minHeight:

Swift
Text("Mindestens 44 hoch")
    .frame(minHeight: 44)

Tipp

Apple empfiehlt, dass antippbare Elemente mindestens 44 × 44 Punkte groß sind. Kleinere Ziele sind schwer zu treffen. .frame(minWidth: 44, minHeight: 44) sorgt dafür.

alignment: Position im Rahmen

Ein frame kann seinen Inhalt auch ausrichten. Damit löst du das Problem aus der letzten Lektion:

Swift
VStack(alignment: .leading) {
    Text("Überschrift")
        .font(.title)
    Text("Untertitel")
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()

Erst maxWidth: .infinity macht den Rahmen bildschirmbreit. Dann schiebt alignment: .leading den Inhalt an den linken Rand. Ohne maxWidth hätte das alignment keinen Platz, in dem es etwas verschieben könnte.

Erwartetes Ergebnis

Überschrift und Untertitel stehen links, mit Standardabstand zum Rand.

Die möglichen Werte kennst du vom ZStack: .topLeading, .top, .topTrailing, .leading, .center, .trailing, .bottomLeading, .bottom, .bottomTrailing.

Swift
Text("Unten rechts")
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
    .padding()

Spacer oder frame?

Beides kann Views an den Rand schieben. HStack { Text("A"); Spacer() } und Text("A").frame(maxWidth: .infinity, alignment: .leading) sehen gleich aus. Das frame-Muster ist kürzer und braucht keinen zusätzlichen Stack. Spacer sind praktischer, wenn zwischen mehreren Elementen Platz verteilt werden soll.

Gleich breite Elemente

Erinnerst du dich an die Statistik-Kacheln aus Übung 2 der letzten Lektion? Sie waren unterschiedlich breit, weil jede nur so breit wie ihr Inhalt war. Mit maxWidth: .infinity an jeder Kachel teilen sie sich den Platz gleichmäßig:

Swift
HStack(spacing: 12) {
    Text("128")
        .frame(maxWidth: .infinity)
        .padding()
        .background(.yellow)
    Text("4.200")
        .frame(maxWidth: .infinity)
        .padding()
        .background(.yellow)
}
.padding()

Weil beide gleich flexibel sind, bekommen sie gleich viel Platz.

Der sichere Bereich

Ein iPhone-Bildschirm hat Zonen, in denen Inhalt schlecht aufgehoben ist: oben die Statusleiste und die Dynamic Island, unten der Home-Balken. Den Bereich dazwischen nennt man Safe Area, den sicheren Bereich.

SwiftUI hält deinen Inhalt automatisch in der Safe Area. Das ist fast immer richtig. Für Hintergründe willst du aber oft, dass sie bis ganz an den Rand gehen:

Swift
ZStack {
    Color.indigo
        .ignoresSafeArea()

    Text("Willkommen")
        .font(.largeTitle)
        .foregroundStyle(.white)
}

Erwartetes Ergebnis

Der ganze Bildschirm ist indigoblau, auch hinter der Statusleiste. Der Text steht trotzdem sicher in der Mitte.

Nur der Hintergrund ignoriert die Safe Area, der Text bleibt brav darin. Genau so sollte es sein.

Achtung

Wende .ignoresSafeArea() nur auf Hintergründe an, nie auf Texte oder Buttons. Sonst können sie hinter der Dynamic Island oder dem Home-Balken verschwinden.

Eine elegante Alternative ist .background mit einer Farbe. Sie dehnt sich automatisch in die Safe Area aus:

Swift
VStack {
    Text("Inhalt")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.indigo)

Ein vollständiges Beispiel

Ein typischer Willkommensbildschirm, der alles aus dieser und der letzten Lektion verbindet:

Swift
struct WelcomeView: View {
    var body: some View {
        VStack(spacing: 24) {
            Spacer()

            Image(systemName: "sparkles")
                .font(.system(size: 72))
                .foregroundStyle(.yellow)

            VStack(spacing: 8) {
                Text("Willkommen")
                    .font(.largeTitle)
                    .bold()
                Text("Schön, dass du da bist. Los geht's mit deiner ersten Gewohnheit.")
                    .multilineTextAlignment(.center)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            Text("Starten")
                .font(.headline)
                .foregroundStyle(.white)
                .frame(maxWidth: .infinity)
                .padding()
                .background(.blue)
                .clipShape(.rect(cornerRadius: 14))
        }
        .padding(.horizontal, 24)
        .padding(.bottom)
    }
}

Erwartetes Ergebnis

Oben und unten verteilter Leerraum, in der Mitte Symbol, Titel und Text, ganz unten ein blauer „Starten“-Knopf über die volle Breite.

Screenshot folgt: Willkommensbildschirm mit Symbol, Titel, Text und breitem Button

Der „Button“ ist hier noch ein gestalteter Text. Wie man echte Buttons baut, lernst du in Lektion 2.11.

Typische Fehler

alignment ohne maxWidth

Swift
Text("Links?")
    .frame(alignment: .leading)

Nichts passiert. Der Rahmen ist so groß wie der Text, also gibt es keinen Platz zum Verschieben. Ergänze maxWidth: .infinity.

background vor frame

Swift
Text("Volle Breite?")
    .background(.blue)
    .frame(maxWidth: .infinity)

Nur der Text ist blau, nicht die ganze Breite. Der Hintergrund gehört nach den Rahmen.

Feste Größen überall

Swift
.frame(width: 375)

Auf einem kleineren iPhone ragt das über den Rand, auf einem größeren bleibt Platz frei. Feste Breiten sind selten eine gute Idee. Nutze maxWidth: .infinity und padding, dann passt sich dein Layout jedem Gerät an.

Inhalt hinter der Dynamic Island

.ignoresSafeArea() wurde auf die ganze View statt nur auf den Hintergrund angewendet. Verschieb den Modifier auf die Farbe oder das Hintergrundbild.

FAQ

Anfänger

Was bedeutet das überhaupt?

Warum sind meine Views alle in der Mitte? Weil SwiftUI Views standardmäßig zentriert. Das ist kein Fehler, sondern die Grundeinstellung. Mit frame(maxWidth: .infinity, alignment: ...) oder Spacern bestimmst du die Position selbst.

Gibt es auch negative Abstände? .padding(-10) funktioniert technisch, ist aber selten eine gute Idee. Willst du Elemente überlappen lassen, sind ZStack, .overlay oder .offset die klareren Werkzeuge.

Wie mache ich eine View genau halb so breit wie den Bildschirm? Dafür gibt es .containerRelativeFrame(.horizontal) { width, _ in width / 2 }. In vielen Fällen ist aber ein HStack mit zwei gleich flexiblen Elementen einfacher.

Fortgeschritten

Warum verhält es sich so?

Was macht fixedSize()? Es sagt einer View: „Nimm genau die Größe, die du idealerweise brauchst, egal was dir angeboten wird.“ Praktisch, wenn ein Text nicht abgeschnitten werden soll: .fixedSize(horizontal: false, vertical: true) lässt ihn in der Höhe wachsen, statt ihn zu kürzen.

Was ist der Unterschied zwischen padding und safeAreaPadding? .safeAreaPadding erweitert den sicheren Bereich, statt einfach Abstand hinzuzufügen. Das ist vor allem bei scrollbaren Inhalten nützlich: Der Inhalt startet eingerückt, darf beim Scrollen aber bis an den Rand. Mehr in Lektion 3.18.

Profi

Was passiert unter der Haube?

Wie funktioniert frame im Layout-Prozess? frame ist selbst eine View. Sie nimmt den Größenvorschlag der Eltern entgegen, passt ihn an ihre Grenzen an (feste Größe, min/max) und gibt diesen veränderten Vorschlag an ihr Kind weiter. Dann wählt sie ihre eigene Größe innerhalb ihrer Grenzen und platziert das Kind gemäß alignment. Deshalb kann ein frame größer sein als sein Inhalt.

Kann ich eigene Ausrichtungen definieren? Ja, mit AlignmentID kannst du eigene Ausrichtungslinien anlegen, etwa um Elemente in verschiedenen Stacks aneinander auszurichten. Das braucht man selten, aber für komplexe Formulare ist es sehr mächtig.

Übungen

Anfänger

Übung 1: Linksbündiger Titel

Baue einen Bildschirmkopf mit einem großen Titel „Einstellungen“ und einem kleinen Untertitel darunter. Beide sollen links stehen, mit 20 Punkten Abstand zum linken Rand und 40 Punkten zum oberen.

Lösung Übung 1Erst selbst probieren!
Swift
VStack(alignment: .leading, spacing: 4) {
    Text("Einstellungen")
        .font(.largeTitle)
        .bold()
    Text("Passe die App an deine Wünsche an")
        .foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
.padding(.top, 40)
.frame(maxHeight: .infinity, alignment: .top)

Der zweite frame schiebt den ganzen Block nach oben, statt ihn vertikal zu zentrieren.

Fortgeschritten

Übung 2: Zwei Buttons unten

Baue am unteren Bildschirmrand zwei gleich breite „Buttons“ nebeneinander: „Abbrechen“ (grau) und „Speichern“ (blau). Beide haben abgerundete Ecken, 12 Punkte Abstand dazwischen und 16 Punkte Abstand zum Rand.

Lösung Übung 2Erst selbst probieren!
Swift
VStack {
    Spacer()

    HStack(spacing: 12) {
        Text("Abbrechen")
            .frame(maxWidth: .infinity)
            .padding()
            .background(Color(.systemGray5))
            .clipShape(.rect(cornerRadius: 12))

        Text("Speichern")
            .foregroundStyle(.white)
            .frame(maxWidth: .infinity)
            .padding()
            .background(.blue)
            .clipShape(.rect(cornerRadius: 12))
    }
    .font(.headline)
    .padding(16)
}
Profi

Übung 3: Vollbild-Hero

Baue einen Bildschirm, dessen obere Hälfte ein Farbverlauf ist, der bis hinter die Statusleiste reicht, mit einem großen weißen Titel unten links darin. Die untere Hälfte enthält normalen Text auf dem Systemhintergrund.

Lösung Übung 3Erst selbst probieren!
Swift
VStack(spacing: 0) {
    Text("Wien")
        .font(.largeTitle)
        .bold()
        .foregroundStyle(.white)
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
        .background(.purple.gradient)

    Text("Die Hauptstadt Österreichs liegt an der Donau und ist für Kaffeehäuser, Musik und Architektur bekannt.")
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}

Beide Hälften sind gleich flexibel und teilen sich die Höhe. Der Verlauf als .background reicht automatisch bis hinter die Statusleiste.

Zusammenfassung

  • padding schafft Abstand, auf allen oder bestimmten Seiten.
  • frame legt einen Rahmen um eine View: fest, minimal oder maximal.
  • .frame(maxWidth: .infinity) macht eine View so breit wie möglich.
  • alignment im frame bestimmt, wo der Inhalt im Rahmen sitzt. Es braucht dafür Platz.
  • Der sichere Bereich schützt Inhalte vor Dynamic Island und Home-Balken. .ignoresSafeArea() nur für Hintergründe.
  • Feste Größen vermeiden, flexible Layouts bevorzugen.

Spickzettel

Swift
.padding()                       .padding(.horizontal, 24)
.padding([.top, .leading], 8)
.frame(width: 200, height: 60)
.frame(maxWidth: .infinity)
.frame(maxWidth: .infinity, alignment: .leading)
.frame(minWidth: 44, minHeight: 44)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
Color.blue.ignoresSafeArea()

Lektion geschafft?

Markiere sie als erledigt, damit du deinen Fortschritt siehst.