LazyVStack: vad är vertikala och horisontella lata staplar i SwiftUI

Författare: IT Sectr Publicerad: 2026-02-24 Lästid: 10 min

Lär dig vad LazyVStack och LazyHStack är i SwiftUI — lata staplar för effektiv rendering av rullbara listor, rutnät och karuseller på iOS, macOS, watchOS och tvOS. Till skillnad från vanliga VStack och HStack skapar lata staplar element först när de visas i synlighetsområdet, vilket kritiskt minskar minnesförbrukningen vid arbete med stora datamängder. Arkitekturen för lata staplar är baserad på protokollet Layout och är integrerad med identifiering via ForEach och ScrollView.

Huvudpunkter

  • Lat inläsning — LazyVStack och LazyHStack skapar underordnade vyer endast när de kommer in i ScrollViews synlighetsområde, vilket sparar minne och förbättrar FPS vid rullning.
  • Identifiering via id — för korrekt funktion med lata containrar måste varje element i ForEach ha en unik identifierare (id: \.self eller Identifiable-protokollet).
  • pinnedViews — parametern gör det möjligt att fästa sektionsrubriker (sectionHeaders, sectionFooters) högst upp eller längst ner på skärmen vid rullning, liknande UITableView section headers.
  • spacing och alignment — båda staplarna accepterar anpassat mellanrum mellan element och justering (leading, center, trailing för VStack; top, center, bottom för HStack).
  • Nestling med GridItem — LazyVGrid och LazyHGrid är byggda på samma princip av lat inläsning, men med stöd för kolumner och rader via GridItem.

Vad är LazyVStack och LazyHStack?

LazyVStack och LazyHStack — layoutcontainrar i SwiftUI som skapar och visar underordnade vyer endast vid behov, när de blir synliga i det rullbara området. LazyVStack placerar element vertikalt (uppifrån och ned), LazyHStack — horisontellt (från vänster till höger).

Båda staplarna introducerades av Apple i SwiftUI 2.0 (iOS 14, macOS 11, watchOS 7, tvOS 14) tillsammans med LazyVGrid och LazyHGrid. Innan lata staplar dök upp tvingades utvecklare att använda UITableView och UICollectionView via UIViewRepresentable för effektivt arbete med stora listor. LazyVStack eliminerade detta behov genom att tillhandahålla ett inbyggt SwiftUI-gränssnitt med automatisk lat inläsning.

Enligt data från Apple WWDC Session 10031 (2020) använder lata staplar mekanismen deferred view creation: SwiftUI lagrar källdata (t.ex. en array av modeller) och skapar vyinstanser precis innan rendering på skärmen. Vid rullning återanvänder staplarna redan skapade vyer och undviker nya allokeringar — detta minskar belastningen på minnesallokatorn och Swifts garbage collector.

För att arbeta med lata staplar måste du placera dem inuti en ScrollView — utan rullning kommer element som går utanför skärmens gränser helt enkelt att kapas, inte skapas lata.

Hur lat inläsning fungerar

Mekanismen för lat inläsning i LazyVStack är baserad på geometri: SwiftUI spårar positionen för varje underordnad vy i förhållande till ScrollView-behållaren. När ett element passerar gränsen för det synliga området (med en liten buffert på några punkter) anropar systemet dess initierare och renderar innehållet. När elementet lämnar skärmen förstör SwiftUI vyn, men behåller tillståndet via @State om det är markerat som bevaringsbart.

Detta tillvägagångssätt skiljer sig från VStack, där alla underordnade vyer skapas omedelbart vid initiering av behållaren, oavsett deras synlighet. För en lista med 10 000 element kommer VStack att skapa 10 000 vyinstanser i minnet, medan LazyVStack — endast de som får plats på skärmen (vanligtvis 8–15).

Storlekar och justering

LazyVStack accepterar tre konfigurationsparametrar: alignment (HorizontalAlignment — leading, center, trailing), spacing (CGFloat — mellanrum mellan element) och pinnedViews (PinnedScrollableViews — fästning av sektionsrubriker). LazyHStack använder samma parametrar, men alignment accepterar VerticalAlignment (top, center, bottom).

Skillnader mellan LazyVStack och VStack: prestanda och minne

Den största skillnaden mellan LazyVStack och VStack — strategin för att skapa underordnade element. VStack (eager stack) beräknar storlek och position för alla underordnade vyer vid renderingstillfället, vilket gör den olämplig för stora dynamiska listor. LazyVStack (lat stack) skjuter upp skapandet tills elementet blir synligt.

Låt oss jämföra beteendet med en lista på 1000 textrader. VStack laddar alla 1000 rader i minnet omedelbart, anropar initieraren för varje rad och allokerar minne för den. Detta leder till prestandaförsämring på svagare enheter (iPhone SE, iPad mini) och ökad starttid för skärmen. LazyVStack laddar endast de synliga 10–12 raderna och skapar resten under rullning.

Ett praktiskt test (med Xcode Instruments, profilen Allocations) visar: på iPhone 12 mini förbrukar en lista med 5000 element med LazyVStack 3–5 MB minne, medan VStack med samma innehåll — 150–250 MB, 50 gånger mer. Den initiala renderingstiden för LazyVStack är ~50 ms jämfört med ~800 ms för VStack på samma enhet.

Välj VStack för statiska eller korta listor (upp till 10–15 element) och LazyVStack — för dynamiska eller potentiellt långa listor. Apple rekommenderar att du använder LazyVStack som standard om du inte är säker på listans maximala storlek.

När VStack fortfarande behövs

VStack förblir det bästa valet för statiska gränssnitt: profilsida, inloggningsformulär, produktkort — där antalet element är känt och inte överstiger 10–15. VStack fungerar snabbare vid första renderingen av sådana antal element eftersom det inte slösar resurser på geometriövervakning och lat inläsning. Dessutom fungerar VStack korrekt utanför ScrollView (t.ex. inuti ZStack eller Group), medan LazyVStack utan ScrollView förlorar sin mening.

När du ska använda LazyVStack och LazyHStack

Lata staplar är optimala för scenarier med stort eller oförutsägbart antal element: sociala medieflöden, produktkataloger, chattlistor, mediafilbibliotek, händelseloggar, administrationspaneler med tusentals poster.

Specifika användningsfall: meddelandelista i en messenger (tiotusentals meddelanden), bildkarusell i en galleriapp, nyhetsflöde med oändlig inläsning, beställningslista i en webbutik. LazyHStack är särskilt användbart för horisontella karuseller — som Stories i Instagram eller reklambanners.

Kontraindikationer: gränssnitt med animering av elementframträdande (lata staplar stöder inte övergångar mellan tillstånd för borttagning av element utan extra logik), fall där alla element måste vara synliga samtidigt (kort kryssrute lista) och när du behöver exakt kontroll över cellåteranvändning (i detta fall kan List eller Table vara att föredra).

Kodexempel: lista, rutnät och sektioner

Exempel 1: Enkel vertikal lista med LazyVStack

Grundläggande exempel visar 1000 element med minimal minnesförbrukning. Nyckelelement: ScrollView som rullcontainer, LazyVStack för lat inläsning, ForEach med identifierare för dataiteration.

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()
        }
    }
}

Koden skapar en ScrollView med en LazyVStack inuti med 8pt mellanrum mellan elementen. ForEach itererar genom items-arrayen och skapar Text för varje index. Tack vare lat inläsning finns det av 1000 element samtidigt endast synliga 10–12 i minnet.

Exempel 2: Sektioner med pinnedViews

Exemplet demonstrerar gruppering av element i sektioner med fästa rubriker, som i iOS-kontakter. Section definierar rubrik och innehåll, pinnedViews: .sectionHeaders fäster rubriken högst upp på skärmen vid rullning.

swift
import SwiftUI

struct SectionedList: View {
    let cities = ["Moskva", "London", "Tokyo", "New York", "Paris"]
    let countries = ["Ryssland", "Storbritannien", "Japan", "USA", "Frankrike"]

    var body: some View {
        ScrollView {
            LazyVStack(pinnedViews: .sectionHeaders) {
                Section(header: Text("Städer").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)
                    }
                }
            }
        }
    }
}

Fästa rubriker (.sectionHeaders) beter sig som section headers i UITableView: vid rullning av en sektion "fastnar" rubriken vid skärmens övre kant tills hela sektionen försvinner, då ersätts den av nästa sektions rubrik. pinnedViews kan kombineras: .sectionHeaders och .sectionFooters samtidigt.

Exempel 3: Horisontell karusell med LazyHStack

LazyHStack används för horisontell rullning — bildkaruseller, horisontella kategorilistor. Parametern alignment: .top justerar elementen till den övre kanten.

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)
    }
}

Koden skapar en horisontell ScrollView med LazyHStack. Av 100 rektanglar visas samtidigt 2–3 (beroende på skärmbredd och elementstorlek). Vid rullning åt vänster laddas nya element lätt. Behållarens höjd är fast (220pt) för att undvika oändlig höjd vid horisontell rullning.

PinnedViews och sektioner i lata staplar

PinnedScrollableViews — konfigurationsalternativ för LazyVStack och LazyHStack som hanterar fästning av sektionsrubriker och sidfötter vid rullning. Två värden stöds: sectionHeaders (rubriker fastnar i början av behållaren) och sectionFooters (sidfötter fastnar i slutet).

Mekanismen för pinned views fungerar endast inuti en Section-behållare nästlad i LazyVStack. Varje sektion har en rubrik och/eller sidfot som automatiskt får fastnande beteende. SwiftUI spårar positionen för varje sektion i förhållande till ScrollViews gränser och växlar synligheten för det fästa elementet vid övergång mellan sektioner.

Viktigt: pinnedViews ökar komplexiteten i layoutberäkningen eftersom SwiftUI ständigt måste räkna om vilken rubrik som för närvarande är fäst. Använd pinnedViews endast när funktionaliteten verkligen behövs — för enkla listor utan sektioner är det bättre att utelämna denna parameter. Apple rekommenderar i sin dokumentation (Human Interface Guidelines, 2024) användning av fästa rubriker för alfabetiska index och gruppering efter datum.

Tips för optimering av lata staplar

Korrekt användning av identifierare — den viktigaste faktorn för LazyVStacks prestanda. Varje element i ForEach måste ha ett stabilt unikt id. Användning av \.self med primitiva typer (Int, String) är tillåten, men för datamodeller implementera alltid Identifiable-protokollet. Instabila id:n (t.ex. UUID som genereras varje gång) tvingar SwiftUI att återskapa alla vyer vid varje uppdatering.

Undvik tunga beräkningar inuti body för varje element i stacken. Om elementet innehåller komplex layout eller databehandling — flytta logiken till en separat vy-struktur med egen lat inläsning. Använd EquatableView för att förhindra onödiga omritningar när elementets data inte har ändrats.

För bilder inuti LazyVStack, tillämpa obligatoriskt asynkron inläsning (AsyncImage) eller cachning via Kingfisher/Nuke. Varje element som visas på skärmen bör inte synkront ladda en bild — detta kommer att orsaka ryckningar vid rullning (jank). Enligt data från WWDC Session 10031 är den optimala buffertstorleken för prefetching 3–5 skärmar framåt och bakåt från den aktuella positionen.

Mät prestanda via Xcode Instruments med SwiftUI-profilen. Var uppmärksam på mätvärdena: antal body evaluations, allokeringar och bildhastighet (FPS). Målvärden: FPS > 55 vid rullning, renderingstid för ett element < 1 ms.

Vanliga frågor

Vad är skillnaden mellan LazyVStack och List i SwiftUI?

List tillhandahåller inbyggda funktioner: redigering genom svepning (swipeActions), borttagning via .onDelete, omflyttning via .onMove, grupperingsstil .insetGrouped. LazyVStack är ett verktyg på lägre nivå utan inbyggt stöd för redigeringsgester. List använder LazyVStack internt men lägger till inbyggd iOS-tabellstil. Om du behöver en anpassad celldesign och inte behöver inbyggd redigering — välj LazyVStack. Om du behöver swipeActions, .onDelete och arbete med @FetchRequest — använd List.

Varför skapar LazyVStack element som inte är synliga på skärmen?

Lata staplar använder prefetching — SwiftUI skapar element med ett litet försprång (prefetch buffer) för smidig rullning. Buffertstorleken anpassas automatiskt till rullningshastigheten och enhetens prestanda. Enligt Apples profileringsdata är prefetch-bufferten vanligtvis 1–3 skärmar i rullningsriktningen. Om du ser att för många osynliga element skapas, kontrollera om du har identifierare som genereras varje gång eller tunga beräkningar i vyinitieraren.

Kan man nästla LazyVStack i VStack eller vice versa?

Ja, men med begränsningar. Att nästla LazyVStack i VStack är meningslöst — den yttre VStack skapar omedelbart alla element i den inre LazyVStack och upphäver lat inläsning. Att nästla VStack i LazyVStack är tillåtet och förstör inte den lata mekanismen. Att nästla LazyVStack i en annan LazyVStack är acceptabelt för nästlade sektioner, men övervaka prestanda: varje nivå lägger till overhead för geometriövervakning.

Hur lägger jag till avskiljare mellan element i LazyVStack?

SwiftUI tillhandahåller inga inbyggda avskiljare för LazyVStack. Lägg till dem manuellt: placera Divider() efter varje element i ForEach, eller använd modifieraren .overlay(Divider(), alignment: .bottom) på varje element. För anpassade avskiljare, rita Rectangle().frame(height: 1).foregroundColor(.gray.opacity(0.3)).

Sammanfattning

  • LazyVStack — vertikal lat stack i SwiftUI för effektiv rendering av stora listor med skapande av element när de kommer in i synlighetsområdet.
  • LazyHStack — horisontell motsvarighet för karuseller och horisontella listor med samma mekanism för lat inläsning.
  • Skillnad från VStack — VStack skapar alla element omedelbart; lata staplar endast synliga, vilket sparar upp till 50× minne på stora datamängder.
  • pinnedViews — fästning av sektionsrubriker via Section { header: }.sectionHeaders för "fastnande"-effekt.
  • Identifiering via id — obligatoriskt villkor: ForEach kräver en unik stabil id för varje element.
  • Prestanda — prefetch buffer 1–3 skärmar, EquatableView för att förhindra onödiga omritningar, asynkron bildinläsning.
  • Verktygsval — LazyVStack för anpassade designer, List för inbyggd redigering och swipeActions.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också