Ismerje meg, mik azok a LazyVStack és LazyHStack a SwiftUI-ben — lusta vermek a görgethető listák, rácsok és körhinták hatékony megjelenítéséhez iOS, macOS, watchOS és tvOS rendszereken. A szokásos VStack és HStack vermekkel ellentétben a lusta vermek csak akkor hozzák létre az elemeket, amikor azok megjelennek a látható területen, ami kritikusan csökkenti a memóriahasználatot nagy adathalmazokkal való munka során. A lusta vermek architektúrája a Layout protokollon alapul, és integrálva van a ForEach és ScrollView általi azonosítással.
Főbb pontok
LazyVStack és LazyHStack — elrendezés konténerek a SwiftUI-ben, amelyek csak szükség esetén hozzák létre és jelenítik meg a gyermek nézeteket, amikor azok láthatóvá válnak a görgethető területen. A LazyVStack függőlegesen (fentről lefelé), a LazyHStack pedig vízszintesen (balról jobbra) helyezi el az elemeket.
Mindkét vermet az Apple mutatta be a SwiftUI 2.0-ban (iOS 14, macOS 11, watchOS 7, tvOS 14) a LazyVGrid és LazyHGrid vermekkel együtt. A lusta vermek megjelenése előtt a fejlesztők kénytelenek voltak UITableView és UICollectionView használni UIViewRepresentable-en keresztül a nagy listákkal való hatékony munkához. A LazyVStack megszüntette ezt a szükségletet, natív SwiftUI felületet biztosítva automatikus lusta betöltéssel.
Az Apple WWDC Session 10031 (2020) adatai szerint a lusta vermek a deferred view creation mechanizmust használják: a SwiftUI tárolja a forrásadatokat (pl. modellek tömbjét), és közvetlenül a képernyőn történő megjelenítés előtt hozza létre a nézet példányokat. Görgetés közben a vermek újrahasznosítják a már létrehozott nézeteket, elkerülve az új allokációkat — ez csökkenti a memória allokátor és a Swift garbage collector terhelését.
A lusta vermekkel való munkához helyezze őket egy ScrollView belsejébe — görgetés nélkül a képernyő határain túlnyúló elemek egyszerűen le lesznek vágva, nem lustán létrehozva.
A lusta betöltés mechanizmusa a LazyVStack-ben geometrián alapul: a SwiftUI követi az egyes gyermek nézetek pozícióját a ScrollView konténerhez képest. Amikor egy elem átlépi a látható terület határát (egy kis, néhány pontos pufferrel), a rendszer meghívja az inicializálóját és megjeleníti a tartalmat. Amikor az elem elhagyja a képernyőt, a SwiftUI megsemmisíti a nézetet, de megtartja az állapotot @State-en keresztül, ha az megőrizhetőként van megjelölve.
Ez a megközelítés eltér a VStack-től, ahol az összes gyermek nézet azonnal létrejön a konténer inicializálásakor, függetlenül azok láthatóságától. Egy 10 000 elemből álló lista esetén a VStack 10 000 nézetpéldányt hoz létre a memóriában, míg a LazyVStack csak azokat, amelyek elférnek a képernyőn (általában 8–15).
A LazyVStack három konfigurációs paramétert fogad el: alignment (HorizontalAlignment — leading, center, trailing), spacing (CGFloat — távolság az elemek között) és pinnedViews (PinnedScrollableViews — szekciófejlécek rögzítése). A LazyHStack ugyanazokat a paramétereket használja, de az alignment VerticalAlignment (top, center, bottom) értéket fogad el.
A fő különbség a LazyVStack és VStack között — a gyermek elemek létrehozásának stratégiája. A VStack (eager stack) kiszámítja az összes gyermek nézet méretét és pozícióját a megjelenítés pillanatában, ami alkalmassá teszi nagy dinamikus listákhoz. A LazyVStack (lusta verem) elhalasztja a létrehozást addig, amíg az elem láthatóvá nem válik.
Hasonlítsuk össze a viselkedést egy 1000 szövegsorból álló lista példáján. A VStack azonnal betölti mind a 1000 sort a memóriába, meghívva minden sor inicializálóját és memóriát foglalva hozzá. Ez gyengébb eszközökön (iPhone SE, iPad mini) teljesítménycsökkenéshez és a képernyő indítási idejének növekedéséhez vezet. A LazyVStack csak a látható 10–12 sort tölti be, a többit görgetés közben hozza létre.
Egy gyakorlati teszt (Xcode Instruments, Allocations profil) azt mutatja: iPhone 12 mini készüléken egy 5000 elemből álló lista LazyVStackkel 3–5 MB memóriát fogyaszt, míg ugyanaz a tartalom VStackkel — 150–250 MB, ami 50-szer több. A kezdeti megjelenítési idő a LazyVStack esetében ~50 ms, szemben a VStack ~800 ms-ával ugyanazon az eszközön.
Válassza a VStack-et statikus vagy rövid listákhoz (10–15 elemig), a LazyVStack-et pedig dinamikus vagy potenciálisan hosszú listákhoz. Az Apple alapértelmezés szerint a LazyVStack használatát ajánlja, ha nem biztos a lista maximális méretében.
A VStack továbbra is a legjobb választás statikus felületekhez: profilképernyő, bejelentkezési űrlap, termékkártya — ahol az elemek száma ismert és nem haladja meg a 10–15-öt. A VStack gyorsabban működik az ilyen mennyiségű elem első megjelenítésekor, mivel nem pazarol erőforrásokat geometria követésére és lusta betöltésre. Ezenkívül a VStack a ScrollView-n kívül is helyesen működik (pl. ZStack vagy Group belsejében), míg a LazyVStack ScrollView nélkül értelmét veszti.
A lusta vermek optimálisak nagy vagy kiszámíthatatlan számú elemű forgatókönyvekhez: közösségi média hírfolyamok, termékkatalógusok, chat listák, médiafájl könyvtárak, eseménynaplók, adminisztrációs panelek több ezer rekorddal.
Konkrét felhasználási esetek: üzenetlista egy messengeren (több tízezer üzenet), képkörhinta egy galéria alkalmazásban, hírfolyam végtelen betöltéssel, rendelési lista egy webáruházban. A LazyHStack különösen hasznos vízszintes körhintákhoz — mint az Instagram Stories vagy promóciós bannerek.
Ellenjavallatok: elemek megjelenési animációjával rendelkező felületek (a lusta vermek nem támogatják az elemeltávolítási állapotok közötti átmeneteket további logika nélkül), olyan esetek, amikor minden elemnek egyszerre kell láthatónak lennie (rövid jelölőnégyzet lista), és amikor pontos vezérlésre van szükség a cellák újrahasznosítása felett (ebben az esetben a List vagy Table lehet előnyösebb).
Az alap példa 1000 elemet jelenít meg minimális memóriahasználattal. Kulcselemek: ScrollView görgető konténerként, LazyVStack a lusta betöltéshez, ForEach azonosítóval az adatok bejárásához.
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()
}
}
}
A kód létrehoz egy ScrollView-t, benne egy LazyVStackkel 8pt távolsággal az elemek között. A ForEach végigmegy az items tömbön, és Text-et hoz létre minden indexhez. A lusta betöltésnek köszönhetően 1000 elemből egyszerre csak a látható 10–12 van a memóriában.
A példa az elemek szekciókba csoportosítását mutatja rögzített fejlécekkel, mint az iOS névjegyekben. A Section meghatározza a fejlécet és a tartalmat, a pinnedViews: .sectionHeaders rögzíti a fejlécet a képernyő tetején görgetés közben.
import SwiftUI
struct SectionedList: View {
let cities = ["Moszkva", "London", "Tokió", "New York", "Párizs"]
let countries = ["Oroszország", "Egyesült Királyság", "Japán", "USA", "Franciaország"]
var body: some View {
ScrollView {
LazyVStack(pinnedViews: .sectionHeaders) {
Section(header: Text("Városok").font(.title).bold()) {
ForEach(cities, id: \.self) { city in
Text(city).padding(8)
}
}
Section(header: Text("Országok").font(.title).bold()) {
ForEach(countries, id: \.self) { country in
Text(country).padding(8)
}
}
}
}
}
}
A rögzített fejlécek (.sectionHeaders) úgy viselkednek, mint az UITableView section headers: a szekció görgetésekor a fejléc „hozzátapad" a képernyő felső széléhez, amíg a teljes szekció el nem tűnik, majd a következő szekció fejléce váltja fel. A pinnedViews kombinálható: .sectionHeaders és .sectionFooters egyszerre.
A LazyHStack vízszintes görgetéshez használatos — képkörhinták, vízszintes kategória listák. A alignment: .top paraméter a felső szélhez igazítja az elemeket.
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)
}
}
A kód létrehoz egy vízszintes ScrollView-t LazyHStackkel. 100 téglalapból egyszerre 2–3 jelenik meg (a képernyő szélességétől és az elemek méretétől függően). Balra görgetve az új elemek lustán töltődnek be. A konténer magassága rögzített (220pt) a végtelen magasság elkerülése érdekében vízszintes görgetésnél.
PinnedScrollableViews — a LazyVStack és LazyHStack konfigurációs opciója, amely a szekciófejlécek és -láblécek rögzítését kezeli görgetés közben. Két érték támogatott: sectionHeaders (fejlécek a konténer elejéhez tapadnak) és sectionFooters (láblécek a végéhez tapadnak).
A pinned views mechanizmus csak a LazyVStack-be ágyazott Section konténeren belül működik. Minden Section rendelkezik fejléccel és/vagy lábléccel, amelyek automatikusan megszerzik a tapadási viselkedést. A SwiftUI követi az egyes szekciók pozícióját a ScrollView határaihoz képest, és átkapcsolja a rögzített elem láthatóságát a szekciók közötti átmenet során.
Fontos: a pinnedViews növeli az elrendezés számításának komplexitását, mivel a SwiftUI-nak folyamatosan újra kell számolnia, hogy melyik fejléc van éppen rögzítve. Csak akkor használja a pinnedViews-t, ha a funkcióra valóban szükség van — egyszerű, szekciók nélküli listák esetén jobb kihagyni ezt a paramétert. Az Apple a dokumentációjában (Human Interface Guidelines, 2024) a rögzített fejlécek használatát ajánlja betűrendes indexekhez és dátum szerinti csoportosításhoz.
Az azonosítók helyes használata — a LazyVStack teljesítményének legfontosabb tényezője. A ForEach minden elemének stabil egyedi id-vel kell rendelkeznie. A \.self használata primitív típusokkal (Int, String) megengedett, de adatmodellek esetén mindig implementálja az Identifiable protokollt. Az instabil id-k (pl. minden alkalommal generált UUID) arra kényszerítik a SwiftUI-t, hogy minden frissítéskor újra létrehozza az összes nézetet.
Kerülje a nehéz számításokat a verem minden elemének body-jában. Ha az elem összetett elrendezést vagy adatfeldolgozást tartalmaz — helyezze át a logikát egy külön nézetstruktúrába saját lusta betöltéssel. Használja a EquatableView-t a szükségtelen újrarajzolások megelőzésére, amikor az elem adatai nem változtak.
A LazyVStack-en belüli képekhez kötelező aszinkron betöltést (AsyncImage) vagy gyorsítótárazást alkalmazni Kingfisher/Nuke segítségével. A képernyőn megjelenő egyes elemek nem tölthetnek be szinkron módon képet — ez görgetési akadásokat (jank) okoz. A WWDC Session 10031 adatai szerint a prefetching optimális puffermérete 3–5 képernyő előre és hátra a jelenlegi pozíciótól.
Mérje a teljesítményt Xcode Instruments segítségével SwiftUI profillal. Figyeljen a metrikákra: body evaluations, allocations és FPS. Célértékek: FPS > 55 görgetés közben, egy elem megjelenítési ideje < 1 ms.
Gyakran Ismételt Kérdések
A List beépített lehetőségeket biztosít: szerkesztés húzással (swipeActions), törlés .onDelete segítségével, áthelyezés .onMove segítségével, csoportosítási stílus .insetGrouped. A LazyVStack egy alacsonyabb szintű eszköz beépített szerkesztési gesztus támogatás nélkül. A List belsőleg LazyVStack-et használ, de natív iOS táblázatstílust ad hozzá. Ha egyedi cellatervezésre van szüksége és nincs szüksége beépített szerkesztésre — válassza a LazyVStack-et. Ha swipeActions, .onDelete és @FetchRequest használatára van szüksége — használja a List-et.
A lusta vermek prefetchinget használnak — a SwiftUI egy kis előrehozással (prefetch buffer) hozza létre az elemeket a gördülékeny görgetés érdekében. A puffer mérete automatikusan alkalmazkodik a görgetés sebességéhez és az eszköz teljesítményéhez. Az Apple profilozási adatai szerint a prefetch buffer általában 1–3 képernyő a görgetés irányában. Ha túl sok láthatatlan elem létrehozását észleli, ellenőrizze, hogy vannak-e minden alkalommal generált azonosítók vagy nehéz számítások a nézet inicializálójában.
Igen, de korlátozásokkal. A LazyVStack VStack-be ágyazása értelmetlen — a külső VStack azonnal létrehozza a belső LazyVStack összes elemét, érvénytelenítve a lusta betöltést. A VStack LazyVStack-be ágyazása megengedett, és nem rontja el a lusta mechanizmust. A LazyVStack másik LazyVStack-be ágyazása elfogadható beágyazott szekciókhoz, de figyelje a teljesítményt: minden szint többletterhelést ad a geometria követéséhez.
A SwiftUI nem biztosít beépített elválasztókat a LazyVStack számára. Adja hozzá őket manuálisan: helyezzen Divider() minden elem után a ForEach-ben, vagy használja a .overlay(Divider(), alignment: .bottom) módosítót minden elemen. Egyedi elválasztókhoz rajzoljon Rectangle().frame(height: 1).foregroundColor(.gray.opacity(0.3)).
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is