LazyVStack: Was sind vertikale und horizontale Stacks in SwiftUI

Autor: IT Sectr Veröffentlicht: 2026-02-24 Lesezeit: 10 Min.

Erfahren Sie, was LazyVStack und LazyHStack in SwiftUI sind — faule Stacks für effizientes Rendern von scrollbaren Listen, Rastern und Karussells auf iOS, macOS, watchOS und tvOS. Im Gegensatz zu normalen VStack und HStack erstellen faule Stacks Elemente nur, wenn sie im sichtbaren Bereich erscheinen, was den Speicherverbrauch bei der Arbeit mit großen Datenmengen kritisch reduziert. Die Architektur der faulen Stacks basiert auf dem Layout-Protokoll und ist mit der Identifikation durch ForEach und ScrollView integriert.

Wichtige Punkte

  • Faule Ladung — LazyVStack und LazyHStack erstellen Unteransichten nur, wenn sie in den sichtbaren Bereich von ScrollView eintreten, sparen Speicher und verbessern FPS beim Scrollen.
  • Identifikation via id — Für korrekte Arbeit mit faulen Containern muss jedes Element in ForEach eine eindeutige Kennung haben (id: \.self oder Identifiable-Protokoll).
  • pinnedViews — Dieser Parameter ermöglicht das Fixieren von Abschnittsköpfen (sectionHeaders, sectionFooters) am oberen oder unteren Bildschirmrand beim Scrollen, ähnlich wie UITableView section headers.
  • spacing und alignment — Beide Stacks akzeptieren benutzerdefinierte Abstände zwischen Elementen und Ausrichtung (leading, center, trailing für VStack; top, center, bottom für HStack).
  • Verschachtelung mit GridItem — LazyVGrid und LazyHGrid basieren auf dem gleichen Prinzip der faulen Ladung, jedoch mit Spalten- und Zeilenunterstützung durch GridItem.

Was sind LazyVStack und LazyHStack?

LazyVStack und LazyHStack sind Layout-Container in SwiftUI, die Unteransichten nur bei Bedarf erstellen und anzeigen, wenn sie im scrollbaren Bereich sichtbar werden. LazyVStack ordnet Elemente vertikal an (von oben nach unten), während LazyHStack sie horizontal anordnet (von links nach rechts).

Beide Stacks wurden von Apple in SwiftUI 2.0 (iOS 14, macOS 11, watchOS 7, tvOS 14) zusammen mit LazyVGrid und LazyHStack eingeführt. Vor dem Erscheinen der faulen Stacks mussten Entwickler UITableView und UICollectionView über UIViewRepresentable verwenden, um effizient mit großen Listen zu arbeiten. LazyVStack beseitigte diese Notwendigkeit durch eine native SwiftUI-Schnittstelle mit automatischer fauler Ladung.

Laut Apple WWDC Session 10031 (2020) verwenden faule Stacks einen Mechanismus der verzögerten Ansichtserstellung: SwiftUI speichert die Quelldaten (z. B. ein Array von Modellen) und erstellt Ansichtsinstanzen unmittelbar vor dem Rendern auf dem Bildschirm. Beim Scrollen wiederverwenden die Stacks bereits erstellte Ansichten und vermeiden neue Zuweisungen — dies reduziert die Belastung des Speicherzuweisers und des Garbage Collectors von Swift.

Um mit faulen Stacks zu arbeiten, platzieren Sie sie immer innerhalb einer ScrollView — ohne Scrollen werden Elemente, die über die Bildschirmgrenzen hinausgehen, einfach beschnitten, nicht faul erstellt.

Wie funktioniert faule Ladung

Der Mechanismus der faulen Ladung in LazyVStack basiert auf Geometrie: SwiftUI verfolgt die Position jeder Unteransicht relativ zum ScrollView-Container. Wenn ein Element die Grenze des sichtbaren Bereichs überschreitet (mit einem kleinen Puffer von einigen Punkten), ruft das System seinen Initialisierer auf und rendert den Inhalt. Wenn ein Element den Bildschirm verlässt, zerstört SwiftUI die Ansicht, bewahrt aber den Zustand über @State, wenn er als erhaltenswert markiert ist.

Dieser Ansatz unterscheidet sich von VStack, wo alle Unteransichten sofort bei der Initialisierung des Containers erstellt werden, unabhängig von ihrer Sichtbarkeit. Für eine Liste von 10.000 Elementen erstellt VStack 10.000 Ansichtsinstanzen im Speicher, während LazyVStack nur diejenigen erstellt, die auf den Bildschirm passen (normalerweise 8–15).

Größen und Ausrichtung

LazyVStack akzeptiert drei Konfigurationsparameter: alignment (HorizontalAlignment — leading, center, trailing), spacing (CGFloat — Abstand zwischen Elementen) und pinnedViews (PinnedScrollableViews — Fixieren von Abschnittsköpfen). LazyHStack verwendet dieselben Parameter, aber alignment akzeptiert VerticalAlignment (top, center, bottom).

Unterschiede zwischen LazyVStack und VStack: Leistung und Speicher

Der Hauptunterschied zwischen LazyVStack und VStack ist die Strategie zur Erstellung von Kind-Elementen. VStack (eifriger Stack) berechnet die Größe und Position aller Unteransichten zum Renderzeitpunkt, was ihn für große dynamische Listen ungeeignet macht. LazyVStack (fauler Stack) verschiebt die Erstellung, bis das Element sichtbar wird.

Vergleichen wir das Verhalten anhand einer Liste von 1000 Textzeilen. VStack lädt alle 1000 Zeilen sofort in den Speicher, ruft den Initialisierer jeder Zeile auf und weist Speicher dafür zu. Dies führt zu Leistungseinbußen auf schwachen Geräten (iPhone SE, iPad mini) und erhöht die Bildschirmstartzeit. LazyVStack lädt nur die sichtbaren 10–12 Zeilen und erstellt den Rest beim Scrollen.

Ein praktischer Test (mit Xcode Instruments, Allocations-Profil) zeigt: Auf einem iPhone 12 mini verbraucht eine Liste von 5000 Elementen mit LazyVStack 3–5 MB Speicher, während VStack mit demselben Inhalt 150–250 MB verbraucht — 50 Mal mehr. Dabei beträgt die anfängliche Renderzeit für LazyVStack ~50 ms gegenüber ~800 ms für VStack auf demselben Gerät.

Wählen Sie VStack für statische oder kurze Listen (bis zu 10–15 Elemente) und LazyVStack für dynamische oder potenziell lange Listen. Apple empfiehlt, standardmäßig LazyVStack zu verwenden, wenn Sie sich über die maximale Listengröße nicht sicher sind.

Wann VStack noch benötigt wird

VStack bleibt die beste Wahl für statische Oberflächen: Profilbildschirm, Anmeldeformular, Produktkarte — wo die Anzahl der Elemente bekannt ist und 10–15 nicht überschreitet. VStack arbeitet bei der Erst-Renderung für solche Mengen schneller, da es keine Ressourcen für Geometrieverfolgung und faule Ladung verschwendet. Außerdem funktioniert VStack korrekt außerhalb von ScrollView (z. B. innerhalb von ZStack oder Group), während LazyVStack ohne ScrollView seinen Zweck verliert.

Wann LazyVStack und LazyHStack verwenden

Faule Stacks sind optimal für Szenarien mit großen oder unvorhersehbaren Elementanzahlen: Social-Media-Feeds, Produktkataloge, Chat-Listen, Medienbibliotheken, Ereignisprotokolle, Administrationspanels mit Tausenden von Datensätzen.

Spezifische Anwendungsfälle: Nachrichtenliste in einem Messenger (Zehntausende von Nachrichten), Bildkarussell in einer Galerie-App, Nachrichtenfeed mit unendlichem Laden, Bestellliste in einem Online-Shop. LazyHStack ist besonders nützlich für horizontale Karussells — zum Beispiel Instagram Stories oder Werbebanner.

Kontraindikationen: Oberflächen mit Animationen zum Erscheinen von Elementen (faule Stacks unterstützen keine Übergänge zwischen Löschzuständen von Elementen ohne zusätzliche Logik), Fälle, in denen alle Elemente gleichzeitig sichtbar sein sollen (eine kurze Liste von Kontrollkästchen), und wenn Sie eine präzise Kontrolle über die Wiederverwendung von Zellen benötigen (in diesem Fall sind List oder Table möglicherweise besser geeignet).

Codebeispiele: Liste, Raster und Abschnitte

Beispiel 1: Einfache vertikale Liste mit LazyVStack

Ein einfaches Beispiel zeigt 1000 Elemente mit minimalem Speicherverbrauch. Schlüsselelemente: ScrollView als Scroll-Container, LazyVStack für faule Ladung, ForEach mit einer Kennung für die Dateniteration.

swift
import SwiftUI

struct LazyListExample: View {
    let items = Array(0..<1000)

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 8) {
                ForEach(items, id: \.self) { index in
                    Text("Element #\(index)")
                        .font(.body)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding()
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(8)
                }
            }
            .padding()
        }
    }
}

Der Code erstellt eine ScrollView mit einem LazyVStack und 8pt Abstand zwischen den Elementen. ForEach iteriert über das items-Array und erstellt für jeden Index einen Text. Dank der faulen Ladung befinden sich von 1000 Elementen nur die sichtbaren 10–12 gleichzeitig im Speicher.

Beispiel 2: Abschnitte mit pinnedViews

Dieses Beispiel zeigt die Gruppierung von Elementen in Abschnitten mit fixierten Köpfen, ähnlich wie bei iOS-Kontakten. Section definiert den Kopf und Inhalt, pinnedViews: .sectionHeaders fixiert den Kopf beim Scrollen am oberen Bildschirmrand.

swift
import SwiftUI

struct SectionedList: View {
    let cities = ["Moskau", "London", "Tokio", "New York", "Paris"]
    let countries = ["Russland", "Vereinigtes Königreich", "Japan", "USA", "Frankreich"]

    var body: some View {
        ScrollView {
            LazyVStack(pinnedViews: .sectionHeaders) {
                Section(header: Text("Städte").font(.title).bold()) {
                    ForEach(cities, id: \.self) { city in
                        Text(city).padding(8)
                    }
                }
                Section(header: Text("Länder").font(.title).bold()) {
                    ForEach(countries, id: \.self) { country in
                        Text(country).padding(8)
                    }
                }
            }
        }
    }
}

Fixierte Köpfe (.sectionHeaders) verhalten sich wie UITableView section headers: Beim Scrollen eines Abschnitts „klebt" der Kopf am oberen Bildschirmrand, bis der gesamte Abschnitt verschwindet, danach wird er durch den Kopf des nächsten Abschnitts ersetzt. pinnedViews können kombiniert werden: .sectionHeaders und .sectionFooters gleichzeitig.

Beispiel 3: Horizontales Karussell mit LazyHStack

LazyHStack wird für horizontales Scrollen verwendet — Bildkarussells, horizontale Kategorielisten. Der Parameter alignment: .top richtet Elemente am oberen Rand aus.

swift
import SwiftUI

struct HorizontalCarousel: View {
    let colors: [Color] = [.red, .blue, .green, .orange, .purple, .pink]

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            LazyHStack(spacing: 16, alignment: .top) {
                ForEach(0..<100, id: \.self) { index in
                    RoundedRectangle(cornerRadius: 12)
                        .fill(colors[index % colors.count])
                        .frame(width: 150, height: 200)
                        .overlay(Text("\(index + 1)").foregroundColor(.white).bold())
                }
            }
            .padding(.horizontal)
        }
        .frame(height: 220)
    }
}

Der Code erstellt eine horizontale ScrollView mit LazyHStack. Von 100 Rechtecken werden nur 2–3 gleichzeitig angezeigt (abhängig von Bildschirmbreite und Elementgröße). Beim Scrollen nach links werden neue Elemente faul geladen. Die Containerhöhe ist fest (220pt), um unendliche Höhe beim horizontalen Scrollen zu vermeiden.

PinnedViews und Abschnitte in faulen Stacks

PinnedScrollableViews ist eine Konfigurationsoption für LazyVStack und LazyHStack, die das Fixieren von Abschnittsköpfen und -füßen beim Scrollen steuert. Zwei Werte werden unterstützt: sectionHeaders (Köpfe haften am Anfang des Containers) und sectionFooters (Füße haften am Ende).

Der Mechanismus der fixierten Ansichten funktioniert nur innerhalb eines in LazyVStack verschachtelten Section-Containers. Jede Section hat einen Kopf und/oder Fuß, die automatisch das Haftverhalten erhalten. SwiftUI verfolgt die Position jedes Abschnitts relativ zu den ScrollView-Grenzen und schaltet die Sichtbarkeit des fixierten Elements beim Übergang zwischen Abschnitten um.

Wichtig: pinnedViews erhöhen die Komplexität der Layoutberechnung, da SwiftUI ständig neu berechnen muss, welcher Kopf gerade fixiert ist. Verwenden Sie pinnedViews nur, wenn die Funktionalität tatsächlich benötigt wird — für einfache Listen ohne Abschnitte ist es besser, diesen Parameter wegzulassen. Apple empfiehlt in seiner Dokumentation (Human Interface Guidelines, 2024) die Verwendung fixierter Köpfe für alphabetische Indizes und datumsbasierte Gruppierung.

Tipps zur Optimierung von faulen Stacks

Die richtige Verwendung von Kennungen ist der wichtigste Leistungsfaktor für LazyVStack. Jedes Element in ForEach muss eine stabile eindeutige ID haben. Die Verwendung von \.self mit primitiven Typen (Int, String) ist akzeptabel, aber für Datenmodelle implementieren Sie immer das Identifiable-Protokoll. Instabile IDs (z. B. jedes Mal generierte UUID) zwingen SwiftUI, alle Ansichten bei jedem Update neu zu erstellen.

Vermeiden Sie schwere Berechnungen innerhalb des body jedes Stack-Elements. Wenn ein Element komplexes Layout oder Datenverarbeitung enthält — extrahieren Sie die Logik in eine separate Ansichtsstruktur mit eigener fauler Ladung. Verwenden Sie EquatableView, um unnötige Neuzeichnungen zu verhindern, wenn sich die Daten des Elements nicht geändert haben.

Für Bilder innerhalb von LazyVStack verwenden Sie immer asynchrones Laden (AsyncImage) oder Caching via Kingfisher/Nuke. Jedes Element sollte beim Erscheinen auf dem Bildschirm kein Bild synchron laden — dies verursacht Ruckeln beim Scrollen. Laut WWDC Session 10031 beträgt die optimale Prefetch-Puffergröße 3–5 Bildschirme vor und hinter der aktuellen Position.

Messen Sie die Leistung mit Xcode Instruments und dem SwiftUI-Profil. Achten Sie auf Metriken: body evaluations, allocations und Bildwiederholrate (FPS). Zielwerte: FPS > 55 beim Scrollen, Renderzeit pro Element < 1 ms.

Häufig gestellte Fragen

Was ist der Unterschied zwischen LazyVStack und List in SwiftUI?

List bietet integrierte Funktionen: Wischbearbeitung (swipeActions), Löschen via .onDelete, Neuanordnung via .onMove, gruppierten Stil .insetGrouped. LazyVStack ist ein niedrigschwelligeres Werkzeug ohne integrierte Unterstützung für Bearbeitungsgesten. List verwendet intern LazyVStack, fügt aber den nativen iOS-Tabellenstil hinzu. Wenn Sie ein benutzerdefiniertes Zellendesign benötigen und keine integrierte Bearbeitung brauchen — wählen Sie LazyVStack. Wenn Sie swipeActions, .onDelete und Arbeit mit @FetchRequest benötigen — verwenden Sie List.

Warum erstellt LazyVStack Elemente, die auf dem Bildschirm nicht sichtbar sind?

Faule Stacks verwenden Prefetching — SwiftUI erstellt Elemente mit einem kleinen Vorauspuffer (Prefetch Buffer), um flüssiges Scrollen zu gewährleisten. Die Puffergröße passt sich automatisch an Scrollgeschwindigkeit und Geräteleistung an. Laut Apple-Profilierungsdaten beträgt der Prefetch-Puffer normalerweise 1–3 Bildschirme in Scrollrichtung. Wenn Sie sehen, dass zu viele unsichtbare Elemente erstellt werden, überprüfen Sie, ob Sie jedes Mal generierte Kennungen oder schwere Berechnungen im Ansichtsinitialisierer haben.

Kann ich LazyVStack in VStack oder umgekehrt verschachteln?

Ja, aber mit Einschränkungen. LazyVStack in VStack zu verschachteln ist sinnlos — der äußere VStack erstellt sofort alle Elemente des inneren LazyVStack und hebt die faule Ladung auf. VStack in LazyVStack zu verschachteln ist in Ordnung und bricht den faulen Mechanismus nicht. LazyVStack in einem anderen LazyVStack zu verschachteln ist für verschachtelte Abschnitte akzeptabel, aber beachten Sie die Leistung: jede Ebene fügt Overhead für die Geometrieverfolgung hinzu.

Wie füge ich Trennlinien zwischen LazyVStack-Elementen hinzu?

SwiftUI bietet keine integrierten Trennlinien für LazyVStack. Fügen Sie sie manuell hinzu: Platzieren Sie Divider() nach jedem Element in ForEach, oder verwenden Sie den .overlay(Divider(), alignment: .bottom)-Modifikator auf jedem Element. Für benutzerdefinierte Trennlinien zeichnen Sie Rectangle().frame(height: 1).foregroundColor(.gray.opacity(0.3)).

Zusammenfassung

  • LazyVStack — ein vertikaler fauler Stack in SwiftUI für effizientes Rendern großer Listen, der Elemente beim Eintritt in den sichtbaren Bereich erstellt.
  • LazyHStack — ein horizontales Gegenstück für Karussells und horizontale Listen mit demselben Mechanismus der faulen Ladung.
  • Unterschied zu VStack — VStack erstellt alle Elemente auf einmal; faule Stacks erstellen nur sichtbare und sparen bis zu 50× Speicher bei großen Datensätzen.
  • pinnedViews — Fixieren von Abschnittsköpfen via Section { header: }.sectionHeaders für einen „Klebeeffekt".
  • Identifikation via id — zwingende Bedingung: ForEach benötigt eine eindeutige stabile ID für jedes Element.
  • Leistung — Prefetch-Puffer von 1–3 Bildschirmen, EquatableView zur Vermeidung unnötiger Neuzeichnungen, asynchrones Bildladen.
  • Werkzeugauswahl — LazyVStack für benutzerdefinierte Designs, List für integrierte Bearbeitung und swipeActions.

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch