Einführung in Dynamic Collection Views in SwiftUI

Moderne iOS-Apps erfordern flüssige, adaptive Schnittstellen, die anmutig auf unterschiedliche Datengrößen, Geräteorientierungen und Bildschirmdimensionen reagieren. In UIKit bieten Sammlungsansichten einen leistungsstarken, aber ausführlichen Mechanismus zum Erstellen gitterartiger Layouts. SwiftUI vereinfacht dies dramatisch mit seiner deklarativen API, insbesondere durch LazyVGrid und LazyHGrid. Diese Komponenten ermöglichen es Entwicklern, dynamische, resizierbare Gitter mit minimalem Code zu definieren, was es einfacher macht, produktionsfähige Benutzeroberflächen zu erstellen, die über iPhones, iPads und sogar Mac Catalyst-Ziele funktionieren.

Ein dynamisches Sammlungsansichtslayout passt seine Spaltenanzahl, Zeilenhöhe, Abstand oder Größenverhalten basierend auf der verfügbaren Containerbreite, der Anzahl der Elemente oder dem Inhalt selbst an. Dieser Artikel führt Sie durch das Erstellen solcher Layouts in SwiftUI und deckt alles ab, vom grundlegenden Rasteraufbau bis hin zu fortgeschrittenem adaptivem Verhalten, Performance-Tuning und Integration mit Animationen. Am Ende haben Sie eine solide Grundlage für die Erstellung flexibler, ansprechender und performanter Sammlungsansichten in Ihren eigenen Apps.

Kernkomponenten: LazyVGrid und LazyHGrid

SwiftUI bietet zwei primäre Rastercontainer: LazyVGrid (vertikales Scrolling-Raster) und LazyHGrid (horizontales Scrolling-Raster). Beide laden ihre Kinder faul, was bedeutet, dass die Ansichten nur dann erstellt werden, wenn sie auf dem Bildschirm sichtbar werden – ein entscheidendes Merkmal für den Umgang mit großen Datensätzen ohne Speicherblasen. Diese Container sind analog zu in UIKit, abzüglich der Boilerplate von Datenquelle und Delegiertenprotokollen.

GridItem-Konfiguration

Um die Struktur eines Rasters zu definieren, erstellen Sie ein Array von GridItem Instanzen. Jede repräsentiert eine einzelne Spalte (in ) oder eine einzelne Zeile (in ).

  • , , , , , , , , , , , , , , ], , , ], ], ], ], ], ], ], ], ], ], ], ], ], [[F
  • Spacing – Horizontaler Abstand zwischen Elementen in derselben Reihe (für vertikale Gitter) oder vertikaler Abstand (für horizontale Gitter).
  • Alignment – Steuert, wie sich Elemente innerhalb einer Gitterzelle ausrichten.

Die Wahl des Größenmodus definiert die Gesamtflexibilität des Layouts:

  • Fixed – Explizite Breite (oder Höhe) für jede Spalte/Zeile. Nützlich, wenn Sie eine genaue Größenbestimmung benötigen, z. B. ein dreispaltiges Layout von festen 100-Punkt-Zellen.
  • Flexibel – Jedes Rasterelement kann sich so ausdehnen, dass es den verfügbaren Speicherplatz mit optionalen Mindest- und Höchstgrenzen ausfüllt. Dies ist die häufigste Wahl für dynamische, gleichmäßig beabstandete Spalten.
  • Adaptive – Das Raster bestimmt automatisch, wie viele Elemente pro Spaltenzeile passen, basierend auf einer festgelegten Mindestgröße. Dies ist der einfachste Weg, um ein dynamisches Layout zu erstellen, das auf die Bildschirmbreite reagiert.

Um beispielsweise ein Raster zu erstellen, das automatisch so viele Spalten wie möglich mit einer Mindestbreite von 100 Punkten passt, würden Sie schreiben:

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

Diese einzelne Zeile ergibt ein Layout, das sich von einer Spalte auf einem schmalen iPhone an mehrere Spalten auf einem iPad in der Landschaft anpasst – genau das Verhalten, das wir für dynamische Layouts wünschen.

Aufbau eines grundlegenden dynamischen Gitters

Beginnen wir mit einem einfachen, voll dynamischen Raster mit -Größe. Unten ist eine vollständige Ansicht, die ein Array von Strings in einem responsiven Raster anzeigt:

struct AdaptiveGridView: View {
 let items = Array(1...20).map { "Item \($0)" }

 var body: some View {
 ScrollView {
 LazyVGrid(
 columns: [GridItem(.adaptive(minimum: 80), spacing: 12)],
 spacing: 12
 ) {
 ForEach(items, id: \.self) { item in
 Text(item)
 .frame(minWidth: 80, maxWidth: .infinity, minHeight: 80)
 .background(Color.blue.opacity(0.2))
 .cornerRadius(8)
 }
 }
 .padding()
 }
 }
}

Hier wird jedes Element auf mindestens 80 Punkte breit und hoch gezwungen. Das Rasterelement berechnet dann automatisch, wie viele Elemente pro Zeile passen können, wenn die verfügbare Breite gegeben ist, wobei die minimale Breite eingehalten wird. Das Ergebnis ist ein Layout, das nahtlos wieder fließt, wenn das Gerät rotiert oder das Fenster verkleinert wird (auf iPad oder Mac).

Dieser Ansatz funktioniert gut für einfache Inhalte, aber für mehr Kontrolle müssen Sie oft mit Elementen kombinieren oder verwenden, um benutzerdefinierte Spaltenzählungen zu berechnen.

Verwendung von GeometryReader für präzise dynamische Spaltenzählungen

Während viele Fälle behandelt, möchten Sie manchmal eine genaue Anzahl von Spalten basierend auf der Containerbreite einstellen – zum Beispiel immer 2 Spalten auf einem iPhone im Porträt, 3 in der Landschaft und 4 auf dem iPad. Die GeometryReader Ansicht gibt die aktuelle Größe des Elternteils an, so dass Sie die optimale Spaltenzahl dynamisch berechnen können.

Beispiel: Eine adaptive Spaltenzahl

Die folgende Ansicht liest die verfügbare Breite und berechnet eine Spaltenzählung basierend auf einer Zielelementbreite:

struct DynamicColumnGridView: View {
 let items = Array(1...30).map { "Item \($0)" }
 let targetItemWidth: CGFloat = 120

 var columns: [GridItem] {
 let count = max(Int(geometryProxy.size.width / targetItemWidth), 1)
 return Array(repeating: GridItem(.flexible(), spacing: 12), count: count)
 }

 var body: some View {
 GeometryReader { geometry in
 ScrollView {
 let width = geometry.size.width
 let count = max(Int(width / targetItemWidth), 1)
 let columns = Array(repeating: GridItem(.flexible(), spacing: 12), count: count)

 LazyVGrid(columns: columns, spacing: 12) {
 ForEach(items, id: \.self) { item in
 Text(item)
 .frame(minWidth: 0, maxWidth: .infinity, minHeight: 80)
 .background(Color.green.opacity(0.3))
 .cornerRadius(8)
 }
 }
 .padding()
 }
 }
 }
}

Wichtig: In der Praxis müssen Sie den -Wert in einer Variablen speichern oder in derselben Schließung verwenden. Das obige Beispiel verwendet aus Gründen der Klarheit eine Helfervariable innerhalb der Schließung. Immer sicherstellen, dass Sie nicht den gesamten Körper unnötig in einwickeln - es kann das Layout unterbrechen, wenn es als oberste Ansicht verwendet wird.

Diese Technik gibt Ihnen die volle Kontrolle über die Anzahl der Spalten, während Sie die Artikel flexibel halten, damit sie die Breite gleichmäßig füllen.Sie können den flexiblen Artikeln auch minimale und maximale Einschränkungen hinzufügen, um extrem gestreckte oder gequetschte Zellen zu vermeiden.

Dynamisch Anpassen von Zeilenhöhen

Dynamische Layouts sind nicht nur Spalten – Zeilenhöhen können auch je nach Inhalt variieren. SwiftUI bietet kein direktes „automatisches Formatierungsraster wie UIKits mit geschätzten Größen. Sie können jedoch ähnliche Ergebnisse mit intrinsischer Inhaltsgröße und oder durch Einbetten datengesteuerter Höhen erzielen.

Zum Beispiel können Sie jede Gitterzelle selbst Größe machen, indem Sie explizite Höhen weglassen und den internen Inhalt die Größe bestimmen lassen. SwiftUI berechnet automatisch die Zellenhöhe basierend auf dem höchsten Element in jeder Zeile (für vertikale Gitter). Aber wenn Sie wirklich variable Zeilenhöhen benötigen (wie ein Mauerwerklayout), benötigen Sie einen benutzerdefinierten Ansatz - oft mit mit benutzerdefinierten Ausrichtungsführungen oder wickeln Sie das Gitter in einem , aber mit unterschiedlichen Höhen pro Spalte. Das ist fortgeschrittener und über den Rahmen dieses Artikels hinaus, aber der Schlüssel zum Mitnehmen ist, dass davon ausgeht, dass alle Zellen in einer Zeile die gleiche Höhe teilen (bestimmt durch die höchste Zelle). Wenn Ihr Design variable Höhen erfordert, sollten Sie eine mit einem benutzerdefinierten Gitterlayout verwenden.

Umgang mit großen Datensätzen mit Lazy Loading

Der Vorteil von und ist ihr faules Ladeverhalten. Sie erstellen nur Ansichten für die Elemente, die derzeit sichtbar sind (plus einen kleinen Puffer). Dies macht sie effizient für Datensätze mit Hunderten oder sogar Tausenden von Elementen.

  • Verwenden Sie stabile Identifikatoren – Geben Sie immer einen eindeutigen -Parameter für an.
  • Vermeiden Sie teure Berechnungen im Körper – Halten Sie Zellansichten einfach.
  • Leverage – Wenn Ihre Zellenansicht mit übereinstimmt oder den Modifikator verwendet, kann SwiftUI Rendering-Änderungen überspringen, wenn sich die Daten der Zelle nicht geändert haben.
  • Wenn du einen in einem anderen faulen Container verschachtelst, kannst du die faule Ladung brechen.

Dynamische Layouts mit Animationen kombinieren

Animationen in SwiftUI können Layoutänderungen natürlich und poliert erscheinen lassen. Wenn sich beispielsweise die Anzahl der Spalten ändert (aufgrund von Rotation oder Fenstergröße), können Sie die Gitterelemente an ihre neuen Positionen animieren. Der einfachste Weg ist, das Gitter in einen -Modifikator zu wickeln oder den -Modifikator mit einem Wert zu verwenden, der Änderungen auslöst.

Animieren von Spaltenänderungen

Hier ist eine Erweiterung des früheren Beispiels für dynamische Spalten mit einem animierten Übergang:

struct AnimatedDynamicGridView: View {
 let items = Array(1...20).map { "Item \($0)" }
 @State private var targetItemWidth: CGFloat = 120

 var body: some View {
 GeometryReader { geometry in
 ScrollView {
 let width = geometry.size.width
 let count = max(Int(width / targetItemWidth), 1)
 let columns = Array(repeating: GridItem(.flexible(), spacing: 12), count: count)

 LazyVGrid(columns: columns, spacing: 12) {
 ForEach(items, id: \.self) { item in
 Text(item)
 .frame(minWidth: 0, maxWidth: .infinity, minHeight: 80)
 .background(Color.orange.opacity(0.4))
 .cornerRadius(8)
 .transition(.scale.combined(with: .opacity))
 }
 }
 .padding()
 .animation(.spring(response: 0.4, dampingFraction: 0.7), value: count)
 }
 }
 }
}

Durch die Verknüpfung der Animation mit dem Wert animiert SwiftUI das Einfügen, Entfernen und Umordnen von Elementen, wenn sich die Anzahl der Spalten ändert.

Praktische Beispiele: Real-World Dynamic Layouts

Lassen Sie uns zwei gängige Szenarien durchgehen, die von dynamischen Layouts für die Sammlungsansicht profitieren.

Fotogalerie-Gitter

Eine typische Fotogalerie verwendet ein quadratisches Zellenraster, das sich an die Bildschirmbreite anpasst. Sie möchten, dass die Zellen so groß wie möglich sind, während eine feste Anzahl von Spalten beibehalten wird (z. B. 2 auf dem iPhone, 3 auf dem iPhone Plus, 4 auf dem iPad).

struct PhotoGalleryGrid: View {
 let images: [String]

 @Environment(\.horizontalSizeClass) var sizeClass

 var columns: [GridItem] {
 let count = sizeClass == .compact ? 2 : 4
 return Array(repeating: GridItem(.flexible(), spacing: 2), count: count)
 }

 var body: some View {
 ScrollView {
 LazyVGrid(columns: columns, spacing: 2) {
 ForEach(images, id: \.self) { imageName in
 Image(imageName)
 .resizable()
 .aspectRatio(1, contentMode: .fill)
 .clipped()
 }
 }
 .padding(.horizontal, 2)
 }
 }
}

Dynamisches Dashboard mit unterschiedlichen Zellgrößen

Für ein Dashboard haben Sie möglicherweise Zellen unterschiedlicher Breiten – z. B. ein großer Graph, der zwei Spalten einnimmt, kleinere Statistiken, die jeweils eine Spalte einnehmen. Sie können dies erreichen, indem Sie Elemente mit expliziten Multiplikatoren kombinieren, indem Sie ein benutzerdefiniertes FLT:44-Array verwenden.

let columns: [GridItem] = [
 GridItem(.flexible(minimum: 80), spacing: 12), // small stat cell
 GridItem(.flexible(minimum: 160), spacing: 12), // wide cell
 GridItem(.flexible(minimum: 80), spacing: 12) // small stat cell
]

Dann können Sie im Raster verschiedene Datentypen verschiedenen Spaltenspannen zuordnen, indem Sie Elemente in einen Container einwickeln, der sich erweitert, um die Rasterbreite zu füllen. Dies erfordert eine sorgfältige Koordination, da nativ Multi-Spalten-Spannen nicht unterstützt. Ein üblicher Workaround ist die Verwendung von Modifikator (verfügbar in iOS 16+) oder die manuelle Indexierung des Rasters, indem Sie Ihre Daten in Abschnitte aufteilen, die jeweils eine ganze Zeile ausfüllen.

Für iOS 16 und höher können Sie den -Modifikator für eine Ansicht innerhalb des Rasters verwenden, um anzugeben, wie viele Spalten diese Ansicht umfassen soll:

LazyVGrid(columns: columns, spacing: 12) {
 ForEach(dashboardItems, id: \.id) { item in
 DashboardCell(item: item)
 .gridCellColumns(item.columnSpan)
 }
}

Dies ist der sauberste Weg, um ein echtes dynamisches Dashboard-Layout mit gemischten Zellengrößen zu erstellen.

Leistungsüberlegungen für große Datensätze

Wenn man mit Tausenden von Gegenständen zu tun hat, kann sogar das faule Laden zu Verzögerungen führen, wenn jede Zelle komplex ist.

  • Prefetch Bilder und Daten – Verwenden Sie oder , um Daten asynchron zu laden, nicht im Körper der Zelle.
  • Verwenden Sie für extrem große Listen – SwiftUIs ist für Tausende von Zeilen mit dynamischer Größe optimiert. Für ein Rasterlayout ist mit einem benutzerdefinierten Layout jedoch möglich, aber schwieriger zu erreichen; Verwenden Sie über , wenn die Leistung zu einem echten Engpass wird.
  • Begrenzt die Neuberechnung des Layouts – Vermeiden Sie es, dynamische Layoutlogik in den Körper der Zelle zu legen.
  • Verwenden Sie auf dem gesamten Gitter – Wenn Sie die Gitterstruktur vollständig ändern (z. B. von 2 Spalten zu 3 wechseln), zwingt das Zuweisen eines neuen zum vollständigen Neuladen, was schneller sein kann, als zu versuchen, eine komplexe Änderung zu animieren.

Externe Ressourcen, um Ihr Wissen zu vertiefen

Um dynamische Sammlungsansichten in SwiftUI weiter zu erkunden, lesen Sie diese offiziellen und Community-Ressourcen:

  1. Apples LazyVGrid Dokumentation – Die offizielle Referenz für alle Parameter und Verhaltensweisen. Apple Developer Dokumentation: LazyVGrid
  2. Quick Start Guide for GridItem – Deckt alle drei Größenstrategien mit interaktiven Beispielen ab. Apple Developer Documentation: GridItem
  3. SwiftUI by Example – Grid Layouts – Ein praktisches Tutorial von Hacking with Swift, das adaptive und flexible Grids abdeckt. Hacking with Swift: Grid Layouts
  4. SwiftUI Lab: Lazy Grids – Tauchen Sie tief in die Leistung und Anpassungen ein. SwiftUI Lab: Lazy Grids

Zusammenfassung der Best Practices

Das Erstellen eines dynamischen Layouts für die Sammlungsansicht in SwiftUI erfordert einen Ausgleich zwischen Flexibilität und Leistung. Hier ist eine kurze Checkliste für produktionsfähige Grids:

  • Beginnen Sie mit für die einfachste dynamische Spaltenzahl; verwenden Sie nur, wenn Sie eine genaue Steuerung benötigen.
  • Geben Sie immer eindeutige Werte an oder entsprechen Sie .
  • Verwenden Sie mit einem Wert, der sich (wie die Spaltenzahl) in glatte Übergänge ändert.
  • Für gemischte Zellengrößen bevorzugen Sie unter iOS 16+ oder entwerfen Sie ein benutzerdefiniertes zeilenweises Layout.
  • Profilieren Sie Ihr Raster mit Instrumenten, um zu überprüfen, ob das faule Laden funktioniert und Zellen keine Offscreen-Inhalte darstellen.
  • Testen Sie auf mehreren Gerätegrößen und -orientierungen - dynamische Layouts, die auf einem Bildschirm gut aussehen, können auf einem anderen brechen.

Die Beherrschung dynamischer Sammlungsansichten ermöglicht es Ihnen, Apps zu erstellen, die sich auf jedem Apple-Gerät nativer und reaktionsfähiger fühlen. Mit den deklarativen Tools von SwiftUI können Sie das erreichen, was einst Dutzende von UIKit-Codezeilen in nur einer Handvoll ausdrucksstarker, wiederverwendbarer Komponenten erforderte.