Ontdek wat LazyVStack en LazyHStack zijn in SwiftUI — luie stacks voor efficiënte weergave van scrollbare lijsten, rasters en carrousels op iOS, macOS, watchOS en tvOS. In tegenstelling tot gewone VStack en HStack, maken luie stacks elementen pas aan wanneer ze in het zichtbare gebied verschijnen, wat het geheugengebruik bij grote datasets aanzienlijk vermindert. De architectuur van luie stacks is gebaseerd op het Layout-protocol en geïntegreerd met identificatie via ForEach en ScrollView.
Belangrijkste punten
LazyVStack en LazyHStack — lay-outcontainers in SwiftUI die onderliggende weergaven alleen maken en tonen wanneer nodig, wanneer ze zichtbaar worden in het scrollbare gebied. LazyVStack plaatst elementen verticaal (van boven naar beneden), en LazyHStack — horizontaal (van links naar rechts).
Beide stacks zijn geïntroduceerd door Apple in SwiftUI 2.0 (iOS 14, macOS 11, watchOS 7, tvOS 14) samen met LazyVGrid en LazyHGrid. Voordat luie stacks verschenen, waren ontwikkelaars gedwongen om UITableView en UICollectionView via UIViewRepresentable te gebruiken voor efficiënt werken met grote lijsten. LazyVStack elimineerde deze noodzaak door een native SwiftUI-interface met automatisch lazy loading te bieden.
Volgens Apple WWDC Session 10031 (2020) gebruiken luie stacks het deferred view creation-mechanisme: SwiftUI slaat brongegevens op (bijv. een array van modellen) en maakt weergave-instanties vlak voordat ze op het scherm worden gerenderd. Tijdens het scrollen hergebruiken stacks reeds gemaakte weergaven, waardoor nieuwe allocaties worden vermeden — dit vermindert de belasting van de geheugenallocator en de garbage collector van Swift.
Om met luie stacks te werken, moet je ze in een ScrollView plaatsen — zonder scrollen worden elementen die buiten de schermgrenzen vallen gewoon afgeknipt, niet lui aangemaakt.
Het lazy loading-mechanisme in LazyVStack is gebaseerd op geometrie: SwiftUI volgt de positie van elke onderliggende weergave ten opzichte van de ScrollView-container. Wanneer een element de grens van het zichtbare gebied overschrijdt (met een kleine buffer van enkele punten), roept het systeem de initialisator aan en rendert de inhoud. Wanneer het element het scherm verlaat, vernietigt SwiftUI de weergave, maar behoudt de status via @State indien gemarkeerd als behouden.
Deze benadering verschilt van VStack, waar alle onderliggende weergaven onmiddellijk worden gemaakt bij initialisatie van de container, ongeacht hun zichtbaarheid. Voor een lijst van 10.000 elementen maakt VStack 10.000 weergave-instanties in het geheugen, terwijl LazyVStack alleen degene maakt die op het scherm passen (meestal 8–15).
LazyVStack accepteert drie configuratieparameters: alignment (HorizontalAlignment — leading, center, trailing), spacing (CGFloat — tussenruimte tussen elementen) en pinnedViews (PinnedScrollableViews — vastzetten van sectiekopteksten). LazyHStack gebruikt dezelfde parameters, maar alignment accepteert VerticalAlignment (top, center, bottom).
Het belangrijkste verschil tussen LazyVStack en VStack — de strategie voor het maken van onderliggende elementen. VStack (eager stack) berekent de grootte en positie van alle onderliggende weergaven op het moment van renderen, wat het ongeschikt maakt voor grote dynamische lijsten. LazyVStack (luie stack) stelt het maken uit tot het moment dat het element zichtbaar wordt.
Laten we het gedrag vergelijken met een lijst van 1000 tekstregels. VStack laadt alle 1000 regels onmiddellijk in het geheugen, roept de initialisator van elke regel aan en wijst er geheugen aan toe. Dit leidt tot prestatievermindering op zwakkere apparaten (iPhone SE, iPad mini) en langere opstarttijd van het scherm. LazyVStack laadt alleen de zichtbare 10–12 regels en maakt de rest tijdens het scrollen.
Een praktische test (met Xcode Instruments, profiel Allocations) toont aan: op iPhone 12 mini verbruikt een lijst van 5000 elementen met LazyVStack 3–5 MB geheugen, terwijl VStack met dezelfde inhoud 150–250 MB verbruikt — 50 keer meer. De initiële rendertijd voor LazyVStack is ~50 ms versus ~800 ms voor VStack op hetzelfde apparaat.
Kies VStack voor statische of korte lijsten (tot 10–15 elementen), en LazyVStack voor dynamische of potentieel lange lijsten. Apple adviseert standaard LazyVStack te gebruiken als je niet zeker bent van de maximale lijstgrootte.
VStack blijft de beste keuze voor statische interfaces: profielscherm, inlogformulier, productkaart — waar het aantal elementen bekend is en niet meer dan 10–15 bedraagt. VStack werkt sneller bij de eerste weergave van een dergelijk aantal elementen, omdat het geen bronnen verspilt aan geometriebewaking en lazy loading. Bovendien werkt VStack correct buiten ScrollView (bijv. binnen ZStack of Group), terwijl LazyVStack zonder ScrollView zijn betekenis verliest.
Luie stacks zijn optimaal voor scenario's met een groot of onvoorspelbaar aantal elementen: social media-feeds, productcatalogi, chatlijsten, mediabibliotheken, gebeurtenislogboeken, beheerpanelen met duizenden records.
Specifieke gebruikssituaties: berichtenlijst in een messenger (tienduizenden berichten), afbeeldingencarrousel in een galerij-app, nieuwsfeed met oneindig scrollen, bestellingenlijst in een webwinkel. LazyHStack is vooral handig voor horizontale carrousels — zoals Stories in Instagram of promotiebanners.
Contra-indicaties: interfaces met verschijningsanimatie van elementen (luie stacks ondersteunen geen overgangen tussen verwijderingsstatussen zonder extra logica), gevallen waarin alle elementen tegelijkertijd zichtbaar moeten zijn (korte checkboxlijst) en wanneer je nauwkeurige controle nodig hebt over celhergebruik (in dit geval kunnen List of Table de voorkeur hebben).
Basisvoorbeeld toont 1000 elementen met minimaal geheugengebruik. Sleutelelementen: ScrollView als scrollcontainer, LazyVStack voor lazy loading, ForEach met identificatie voor gegevensiteratie.
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()
}
}
}
De code maakt een ScrollView met daarin een LazyVStack met 8pt tussenruimte tussen elementen. ForEach doorloopt de items-array en maakt Text voor elke index. Dankzij lazy loading bevinden zich van 1000 elementen tegelijkertijd slechts 10–12 zichtbare in het geheugen.
Voorbeeld demonstreert het groeperen van elementen in secties met vastgezette kopteksten, zoals in iOS-contacten. Section definieert koptekst en inhoud, pinnedViews: .sectionHeaders zet de koptekst bovenaan het scherm vast tijdens het scrollen.
import SwiftUI
struct SectionedList: View {
let cities = ["Moskou", "Londen", "Tokio", "New York", "Parijs"]
let countries = ["Rusland", "Verenigd Koninkrijk", "Japan", "Verenigde Staten", "Frankrijk"]
var body: some View {
ScrollView {
LazyVStack(pinnedViews: .sectionHeaders) {
Section(header: Text("Steden").font(.title).bold()) {
ForEach(cities, id: \.self) { city in
Text(city).padding(8)
}
}
Section(header: Text("Landen").font(.title).bold()) {
ForEach(countries, id: \.self) { country in
Text(country).padding(8)
}
}
}
}
}
}
Vastgezette kopteksten (.sectionHeaders) gedragen zich als section headers in UITableView: tijdens het scrollen van een sectie "plakt" de koptekst aan de bovenrand van het scherm totdat de hele sectie verdwijnt, waarna deze wordt vervangen door de koptekst van de volgende sectie. pinnedViews kunnen worden gecombineerd: .sectionHeaders en .sectionFooters tegelijkertijd.
LazyHStack wordt gebruikt voor horizontaal scrollen — afbeeldingencarrousels, horizontale categorielijsten. De parameter alignment: .top lijnt elementen uit aan de bovenrand.
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)
}
}
De code maakt een horizontale ScrollView met LazyHStack. Van de 100 rechthoeken worden er tegelijkertijd 2–3 weergegeven (afhankelijk van schermbreedte en elementgrootte). Bij scrollen naar links worden nieuwe elementen lui geladen. De containerhoogte is vast (220pt) om oneindige hoogte bij horizontaal scrollen te voorkomen.
PinnedScrollableViews — configuratieoptie van LazyVStack en LazyHStack die het vastzetten van sectiekopteksten en -voetteksten tijdens het scrollen beheert. Twee waarden worden ondersteund: sectionHeaders (kopteksten plakken aan het begin van de container) en sectionFooters (voetteksten plakken aan het einde).
Het pinned views-mechanisme werkt alleen binnen een Section-container genest in LazyVStack. Elke sectie heeft een koptekst en/of voettekst die automatisch het plakgedrag krijgen. SwiftUI volgt de positie van elke sectie ten opzichte van de ScrollView-grenzen en schakelt de zichtbaarheid van het vastgezette element bij overgang tussen secties.
Belangrijk: pinnedViews verhogen de complexiteit van de lay-outberekening omdat SwiftUI constant moet herbereken welke koptekst momenteel is vastgezet. Gebruik pinnedViews alleen wanneer de functionaliteit echt nodig is — voor eenvoudige lijsten zonder secties kun je deze parameter beter weglaten. Apple adviseert in zijn documentatie (Human Interface Guidelines, 2024) het gebruik van vastgezette kopteksten voor alfabetische indexen en groepering op datum.
Correct gebruik van identificaties — de belangrijkste factor voor de prestaties van LazyVStack. Elk element in ForEach moet een stabiele unieke id hebben. Het gebruik van \.self met primitieve typen (Int, String) is toegestaan, maar voor gegevensmodellen implementeer je altijd het Identifiable-protocol. Instabiele id's (bijv. elke keer gegenereerde UUID) dwingen SwiftUI om alle weergaven opnieuw te maken bij elke update.
Vermijd zware berekeningen in de body van elk element van de stack. Als een element complexe lay-out of gegevensverwerking bevat — verplaats de logica naar een aparte weergavestructuur met eigen lazy loading. Gebruik EquatableView om onnodige hertekeneningen te voorkomen wanneer de gegevens van het element niet zijn gewijzigd.
Voor afbeeldingen in LazyVStack moet je asynchroon laden (AsyncImage) of caching via Kingfisher/Nuke toepassen. Elk element dat op het scherm verschijnt, mag niet synchroon een afbeelding laden — dit veroorzaakt haperingen tijdens het scrollen (jank). Volgens WWDC Session 10031 is de optimale buffergrootte voor prefetching 3–5 schermen vooruit en achteruit vanaf de huidige positie.
Meet prestaties via Xcode Instruments met het SwiftUI-profiel. Let op de metrieken: aantal body-evaluaties, allocaties en framesnelheid (FPS). Streefwaarden: FPS > 55 tijdens scrollen, rendertijd van één element < 1 ms.
Veelgestelde vragen
List biedt ingebouwde mogelijkheden: bewerken door te vegen (swipeActions), verwijderen via .onDelete, herschikken via .onMove, groeperingsstijl .insetGrouped. LazyVStack is een lager niveau-gereedschap zonder ingebouwde ondersteuning voor bewerkingsgebaren. List gebruikt LazyVStack intern, maar voegt een native iOS-tabelstijl toe. Als je een aangepast celontwerp nodig hebt en geen ingebouwde bewerking — kies dan LazyVStack. Als je swipeActions, .onDelete en werken met @FetchRequest nodig hebt — gebruik dan List.
Luie stacks gebruiken prefetching — SwiftUI maakt elementen met een kleine vooruitloop (prefetch buffer) om scrollen soepel te laten verlopen. De buffergrootte wordt automatisch aangepast aan de scrollsnelheid en apparaatprestaties. Volgens Apple-profilering is de prefetch buffer meestal 1–3 schermen in de scrollrichting. Als je ziet dat er te veel onzichtbare elementen worden gemaakt, controleer dan of je identificaties hebt die elke keer worden gegenereerd of zware berekeningen in de weergave-initialisator.
Ja, maar met beperkingen. LazyVStack in VStack nesten is zinloos — de buitenste VStack maakt alle elementen van de binnenste LazyVStack onmiddellijk, waardoor lazy loading teniet wordt gedaan. VStack in LazyVStack nesten is toegestaan en breekt het luie mechanisme niet. LazyVStack in een andere LazyVStack nesten is acceptabel voor geneste secties, maar houd prestaties in de gaten: elk niveau voegt overhead toe voor geometriebewaking.
SwiftUI biedt geen ingebouwde scheidingstekens voor LazyVStack. Voeg ze handmatig toe: plaats Divider() na elk element in ForEach, of gebruik de modifier .overlay(Divider(), alignment: .bottom) op elk element. Voor aangepaste scheidingstekens teken je Rectangle().frame(height: 1).foregroundColor(.gray.opacity(0.3)).
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook