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
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.
So funktioniert es:
columnsbeschreibt die Spalten: ein Array aus **GridItem**s. Drei Einträge ergeben drei Spalten.LazyVGridverteilt die Inhalte von links nach rechts und dann Reihe für Reihe nach unten.spacingist der Abstand zwischen den Reihen. Den Abstand zwischen Spalten bestimmen dieGridItems.- Das Grid steckt in einer
ScrollView. Anders alsListscrollt 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
GridItem(.fixed(100))Die Spalte ist genau 100 Punkte breit, egal wie viel Platz es gibt.
Flexibel: .flexible
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
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.
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?
.adaptivefür Galerien und Kacheln, die sich jedem Bildschirm anpassen sollen. Am häufigsten..flexible, wenn die Anzahl der Spalten feststehen soll, etwa immer zwei..fixedfü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:
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:
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:
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:
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:
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
GridRowist eine Zeile. - Die Spalten passen sich automatisch dem breitesten Inhalt an. Genau das ist mit verschachtelten
HStacks fast unmöglich. - Ein
Dividerzwischen den Zeilen geht über die ganze Breite.
Einzelne Zellen kannst du anders ausrichten oder über mehrere Spalten ziehen:
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.Griderzeugt 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
[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
Ü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!
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.
Ü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.
Ü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
LazyVGridordnet viele Elemente in Spalten an und erzeugt nur die sichtbaren. Es braucht eineScrollView.- Spalten mit
GridItem:.fixed,.flexibleoder.adaptive. Ein einziges adaptives Item passt sich jeder Breite an. .aspectRatio(1, contentMode: .fit)macht Kacheln quadratisch.pinnedViews: .sectionHeaderslässt Überschriften beim Scrollen kleben.LazyHGridscrollt waagrecht und wird über Reihen beschrieben.GridmitGridRowfür Tabellen mit exakt ausgerichteten Spalten.
Spickzettel
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) }
}