Lektion 3.17 · Stufe 3 – Fortgeschritten

Grids

ca. 15 Minuten Lesezeit

Lernziel

Nach dieser Lektion kannst du Inhalte in Rastern anordnen: mit LazyVGrid und LazyHGrid für viele, scrollbare Elemente wie Galerien und mit Grid für tabellenartige Layouts mit ausgerichteten Spalten. Du kennst die drei Spaltenarten fest, flexibel und adaptiv.

Das Problem: Mehr als eine Spalte

Listen zeigen Inhalte untereinander, eine Zeile pro Eintrag. Für viele Inhalte ist ein Raster aber viel besser geeignet:

  • Fotos in einer Galerie
  • App-Symbole auf dem Home-Bildschirm
  • Produkte in einem Shop
  • Kategorien als Kacheln, wie in der Rezept-App aus Projekt D

Mit HStack und VStack (Lektion 2.7) kannst du ein festes Raster von Hand bauen. Aber wie viele Spalten passen auf ein kleines iPhone, wie viele auf ein großes, wie viele im Querformat? Und was ist mit 200 Fotos? Dafür gibt es Grids.

LazyVGrid: Ein scrollbares Raster

Swift
struct ColorGrid: View {
    let colors: [Color] = [.red, .orange, .yellow, .green, .mint, .teal, .cyan, .blue, .indigo, .purple, .pink, .brown]

    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 12) {
                ForEach(colors, id: \.self) { color in
                    RoundedRectangle(cornerRadius: 12)
                        .fill(color)
                        .frame(height: 100)
                }
            }
            .padding()
        }
    }
}

Erwartetes Ergebnis

Drei gleich breite Spalten mit farbigen Kacheln, vier Reihen untereinander. Bei mehr Farben kann man scrollen.

Screenshot folgt: Raster mit farbigen Kacheln in drei Spalten

So funktioniert es:

  • columns beschreibt die Spalten: ein Array aus **GridItem**s. Drei Einträge ergeben drei Spalten.
  • LazyVGrid verteilt die Inhalte von links nach rechts und dann Reihe für Reihe nach unten.
  • spacing ist der Abstand zwischen den Reihen. Den Abstand zwischen Spalten bestimmen die GridItems.
  • Das Grid steckt in einer ScrollView. Anders als List scrollt ein Grid nicht von selbst.

Warum „Lazy“?

Lazy heißt „faul“: Das Grid erzeugt nur die Views, die gerade sichtbar sind oder gleich sichtbar werden. Bei 1.000 Fotos werden nicht 1.000 Views auf einmal gebaut, sondern nur die paar Dutzend auf dem Bildschirm. Beim Scrollen kommen weitere dazu. Das hält die App schnell und sparsam, genau wie bei List (Lektion 2.15).

Drei Arten von Spalten

Das Interessante an Grids sind die GridItems. Es gibt drei Arten:

Fest: .fixed

Swift
GridItem(.fixed(100))

Die Spalte ist genau 100 Punkte breit, egal wie viel Platz es gibt.

Flexibel: .flexible

Swift
GridItem(.flexible())
GridItem(.flexible(minimum: 80, maximum: 200))

Die Spalte teilt sich den verfügbaren Platz mit anderen flexiblen Spalten. Optional mit Mindest- und Höchstbreite. Drei flexible Spalten sind also immer genau drei, nur unterschiedlich breit je nach Bildschirm.

Adaptiv: .adaptive

Swift
let columns = [GridItem(.adaptive(minimum: 100))]

Das ist der Clou: Ein einziges adaptives GridItem erzeugt so viele Spalten, wie hineinpassen, jede mindestens 100 Punkte breit. Auf einem kleinen iPhone sind es vielleicht drei, auf einem großen vier, im Querformat sieben, auf dem iPad zehn.

Swift
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100), spacing: 12)], spacing: 12) {
    ForEach(colors, id: \.self) { color in
        RoundedRectangle(cornerRadius: 12)
            .fill(color)
            .frame(height: 100)
    }
}

Dreh den Simulator ins Querformat (⌘ →), und das Raster ordnet sich automatisch neu.

Welche Art wann?

  • .adaptive für Galerien und Kacheln, die sich jedem Bildschirm anpassen sollen. Am häufigsten.
  • .flexible, wenn die Anzahl der Spalten feststehen soll, etwa immer zwei.
  • .fixed für einzelne Spalten mit fester Breite, etwa eine schmale Spalte für Symbole.

Die Arten lassen sich mischen: [GridItem(.fixed(40)), GridItem(.flexible())] ergibt eine schmale und eine breite Spalte.

Quadratische Kacheln

Häufig sollen Kacheln quadratisch sein, so hoch wie breit. Weil die Breite bei flexiblen Spalten vom Bildschirm abhängt, kannst du keine feste Höhe angeben. Hier hilft .aspectRatio:

Swift
Color.blue
    .aspectRatio(1, contentMode: .fit)
    .clipShape(.rect(cornerRadius: 12))

.aspectRatio(1, ...) heißt: Breite zu Höhe ist 1 zu 1. Die Kachel nimmt die Spaltenbreite und wird genauso hoch.

Für Fotos funktioniert das so:

Swift
Color.clear
    .aspectRatio(1, contentMode: .fit)
    .overlay {
        AsyncImage(url: photo.url) { image in
            image.resizable().scaledToFill()
        } placeholder: {
            Color(.secondarySystemBackground)
        }
    }
    .clipShape(.rect(cornerRadius: 8))

Ein durchsichtiges, quadratisches Color.clear gibt die Form vor, und das Bild liegt mit scaledToFill darüber. So hat jede Kachel garantiert dieselbe Größe, egal welches Seitenverhältnis das Foto hat.

Abschnitte mit Überschriften

Wie Listen können Grids Abschnitte haben:

Swift
LazyVGrid(columns: columns, pinnedViews: .sectionHeaders) {
    Section {
        ForEach(fruits, id: \.self) { Text($0) }
    } header: {
        Text("Obst")
            .font(.headline)
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.vertical, 8)
            .background(.background)
    }
}

Mit pinnedViews: .sectionHeaders bleibt die Überschrift beim Scrollen oben „kleben“, bis der nächste Abschnitt sie verdrängt, wie in der Kontakte-App.

LazyHGrid: Waagrecht scrollen

Das Gegenstück scrollt seitwärts. Statt Spalten beschreibst du Reihen:

Swift
ScrollView(.horizontal) {
    LazyHGrid(rows: [GridItem(.fixed(80)), GridItem(.fixed(80))], spacing: 12) {
        ForEach(1...20, id: \.self) { number in
            Text("\(number)")
                .frame(width: 80, height: 80)
                .background(.orange.opacity(0.3))
                .clipShape(.rect(cornerRadius: 8))
        }
    }
    .padding()
}

Zwei Reihen à 80 Punkte, und die Elemente werden spaltenweise von oben nach unten und dann nach rechts verteilt. Typisch für Karussells wie „Zuletzt gehört“ in Musik-Apps.

Grid: Tabellen mit ausgerichteten Spalten

LazyVGrid ist für viele gleichartige Elemente. Für tabellenartige Layouts, in denen Spalten über mehrere Zeilen hinweg sauber ausgerichtet sein sollen, gibt es Grid:

Swift
Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 8) {
    GridRow {
        Text("Produkt").bold()
        Text("Menge").bold()
        Text("Preis").bold()
    }
    Divider()
    GridRow {
        Text("Kaffee")
        Text("2")
        Text("7,00 €")
    }
    GridRow {
        Text("Croissant mit Butter")
        Text("1")
        Text("2,80 €")
    }
}
.padding()

Erwartetes Ergebnis

Eine kleine Tabelle mit drei Spalten. Die Spalten sind so breit wie ihr breitester Inhalt, und alle Werte stehen exakt untereinander.

  • Jede GridRow ist eine Zeile.
  • Die Spalten passen sich automatisch dem breitesten Inhalt an. Genau das ist mit verschachtelten HStacks fast unmöglich.
  • Ein Divider zwischen den Zeilen geht über die ganze Breite.

Einzelne Zellen kannst du anders ausrichten oder über mehrere Spalten ziehen:

Swift
GridRow {
    Text("Summe")
        .gridCellColumns(2)
    Text("9,80 €")
        .bold()
        .gridColumnAlignment(.trailing)
}
  • .gridCellColumns(2) lässt eine Zelle über zwei Spalten gehen.
  • .gridColumnAlignment(.trailing) richtet die ganze Spalte rechtsbündig aus, gut für Beträge.

LazyVGrid oder Grid?

  • LazyVGrid: viele, gleichartige Elemente, scrollbar, nur Sichtbares wird erzeugt. Galerien, Kacheln.
  • Grid: wenige Zeilen, Spalten sollen sich nach dem Inhalt richten und exakt ausgerichtet sein. Tabellen, Formulare, Statistiken. Grid erzeugt alle Zellen sofort.

Typische Fehler

Grid scrollt nicht

Ein LazyVGrid scrollt nicht von selbst. Pack es in eine ScrollView.

Kacheln haben unterschiedliche Höhen

In jeder Reihe richtet sich die Höhe nach dem höchsten Element. Unterschiedlich lange Texte oder Bilder mit verschiedenen Seitenverhältnissen ergeben unruhige Reihen. Gib den Kacheln eine feste Höhe oder ein festes Seitenverhältnis.

.adaptive mit mehreren Einträgen

Swift
[GridItem(.adaptive(minimum: 100)), GridItem(.adaptive(minimum: 100))]

Das ergibt zwei Bereiche, die sich jeweils mit Spalten füllen, und meist ein seltsames Ergebnis. Für ein gleichmäßiges, anpassungsfähiges Raster reicht ein adaptives GridItem.

Alles in Grid bei vielen Daten

Grid ist nicht lazy. Mit Tausenden Zeilen wird es langsam. Nimm dafür LazyVGrid oder List.

FAQ

Anfänger

Was bedeutet das überhaupt?

Kann ich ein Grid in einer List verwenden? Technisch ja, aber Listen haben eigene Zeilenstile, die mit Grids oft unschön zusammenspielen. Für Raster-Bildschirme nimm eine ScrollView als äußeren Container.

Wie mache ich Kacheln antippbar? Wie jede andere View: mit einem Button oder einem NavigationLink um die Kachel. Gib dem Button .buttonStyle(.plain), damit die Kachel nicht blau eingefärbt wird.

Fortgeschritten

Warum verhält es sich so?

Wie animiere ich das Einfügen und Entfernen von Kacheln? Mit .animation(.default, value: items) am Grid und Identifiable-Elementen. Neue Kacheln blenden ein, und die anderen rutschen an ihre neue Position. Mehr zu Animationen ab Lektion 3.23.

Kann ich die Spaltenanzahl selbst bestimmen, abhängig von der Bildschirmgröße? Ja, etwa mit @Environment(\.horizontalSizeClass) (Lektion 3.3): bei .compact zwei, bei .regular vier flexible Spalten. Meist ist .adaptive aber die elegantere Lösung, weil es auf die tatsächliche Breite reagiert.

Profi

Was passiert unter der Haube?

Wie berechnet .adaptive die Spaltenanzahl? Es teilt die verfügbare Breite durch die Mindestbreite plus Abstand und rundet ab. Die übrige Breite wird gleichmäßig auf die Spalten verteilt, bis zur optionalen Höchstbreite. Deshalb sind adaptive Spalten meist etwas breiter als ihr Minimum.

Warum kann LazyVGrid keine unterschiedlich hohen Kacheln wie ein Mauerwerk-Layout? Lazy-Grids ordnen in festen Reihen an, damit sie nur sichtbare Zeilen erzeugen und Positionen schnell berechnen können. Ein Layout wie bei Pinterest, bei dem Kacheln unterschiedlicher Höhe lückenlos gestapelt werden, baust du mit dem Layout-Protokoll (Lektion 4.6) oder mehreren LazyVStacks nebeneinander.

Übungen

Anfänger

Übung 1: Emoji-Raster

Zeig 30 Emojis in einem adaptiven Raster mit Mindestbreite 60. Jedes Emoji steht groß in einer quadratischen, hellgrauen Kachel mit abgerundeten Ecken. Dreh den Simulator und beobachte die Spaltenanzahl.

Lösung Übung 1Erst selbst probieren!
Swift
struct EmojiGrid: View {
    let emojis = Array("🍎🍐🍊🍋🍌🍉🍇🍓🫐🍈🍒🍑🥭🍍🥥🥝🍅🍆🥑🥦🥬🥒🌶️🫑🌽🥕🧄🧅🥔🍠").map(String.init)

    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 60), spacing: 8)], spacing: 8) {
                ForEach(emojis, id: \.self) { emoji in
                    Text(emoji)
                        .font(.largeTitle)
                        .frame(maxWidth: .infinity)
                        .aspectRatio(1, contentMode: .fit)
                        .background(Color(.secondarySystemBackground))
                        .clipShape(.rect(cornerRadius: 12))
                }
            }
            .padding()
        }
    }
}

Array("…").map(String.init) zerlegt einen Text in einzelne Zeichen und macht daraus Strings.

Fortgeschritten

Übung 2: Kategorie-Kacheln

Baue für die Rezept-App aus Projekt D einen Startbildschirm, der alle Kategorien als Kacheln in zwei flexiblen Spalten zeigt: Bild oben, Name darunter. Ein Tipp öffnet die Rezeptliste der Kategorie.

Profi

Übung 3: Monatsübersicht

Baue mit Grid eine Übersicht für einen Monat: Spalten „Kategorie“, „Anzahl“, „Summe“ und „Anteil“. Die Zahlen sind rechtsbündig, unten steht eine Summenzeile, deren erste Zelle über zwei Spalten geht. Verwende vier erfundene Kategorien mit Beträgen.

Zusammenfassung

  • LazyVGrid ordnet viele Elemente in Spalten an und erzeugt nur die sichtbaren. Es braucht eine ScrollView.
  • Spalten mit GridItem: .fixed, .flexible oder .adaptive. Ein einziges adaptives Item passt sich jeder Breite an.
  • .aspectRatio(1, contentMode: .fit) macht Kacheln quadratisch.
  • pinnedViews: .sectionHeaders lässt Überschriften beim Scrollen kleben.
  • LazyHGrid scrollt waagrecht und wird über Reihen beschrieben.
  • Grid mit GridRow für Tabellen mit exakt ausgerichteten Spalten.

Spickzettel

Swift
ScrollView {
    LazyVGrid(columns: [GridItem(.adaptive(minimum: 100), spacing: 12)], spacing: 12) {
        ForEach(items) { item in
            Tile(item: item)
                .aspectRatio(1, contentMode: .fit)
        }
    }
    .padding()
}

let columns = [GridItem(.flexible()), GridItem(.flexible())]

Grid(alignment: .leading) {
    GridRow { Text("A"); Text("1") }
    GridRow { Text("Summe").gridCellColumns(2) }
}

Lektion geschafft?

Markiere sie als erledigt, damit du deinen Fortschritt siehst.