LazyVStack: co jsou vertikální a horizontální líné zásobníky ve SwiftUI

Autor: IT Sectr Publikováno: 2026-02-24 Doba čtení: 10 min

Zjistěte, co jsou LazyVStack a LazyHStack ve SwiftUI — líné zásobníky pro efektivní vykreslování posouvatelných seznamů, mřížek a karuselů na iOS, macOS, watchOS a tvOS. Na rozdíl od běžných VStack a HStack, líné zásobníky vytvářejí prvky až když se objeví v oblasti viditelnosti, což kriticky snižuje spotřebu paměti při práci s velkými datovými sadami. Architektura líných zásobníků je založena na protokolu Layout a je integrována s identifikací přes ForEach a ScrollView.

Hlavní body

  • Líné načítání — LazyVStack a LazyHStack vytvářejí podřízené pohledy až když vstoupí do viditelné oblasti ScrollView, šetří paměť a zlepšují FPS při posouvání.
  • Identifikace přes id — pro správnou funkci s línými kontejnery musí mít každý prvek ve ForEach jedinečný identifikátor (id: \.self nebo protokol Identifiable).
  • pinnedViews — parametr umožňuje připínání záhlaví sekcí (sectionHeaders, sectionFooters) k horní nebo dolní části obrazovky při posouvání, podobně jako UITableView section headers.
  • spacing a alignment — oba zásobníky přijímají vlastní mezeru mezi prvky a zarovnání (leading, center, trailing pro VStack; top, center, bottom pro HStack).
  • Vnoření s GridItem — LazyVGrid a LazyHGrid jsou postaveny na stejném principu líného načítání, ale s podporou sloupců a řádků přes GridItem.

Co jsou LazyVStack a LazyHStack?

LazyVStack a LazyHStack — kontejnery rozvržení ve SwiftUI, které vytvářejí a zobrazují podřízené pohledy pouze v případě potřeby, když se stanou viditelnými v posouvatelné oblasti. LazyVStack umísťuje prvky vertikálně (shora dolů), LazyHStack — horizontálně (zleva doprava).

Oba zásobníky byly představeny Apple ve SwiftUI 2.0 (iOS 14, macOS 11, watchOS 7, tvOS 14) spolu s LazyVGrid a LazyHGrid. Před příchodem líných zásobníků byli vývojáři nuceni používat UITableView a UICollectionView přes UIViewRepresentable pro efektivní práci s velkými seznamy. LazyVStack tuto potřebu odstranil poskytnutím nativního rozhraní SwiftUI s automatickým líným načítáním.

Podle údajů Apple WWDC Session 10031 (2020) používají líné zásobníky mechanismus deferred view creation: SwiftUI ukládá zdrojová data (např. pole modelů) a vytváří instance pohledů těsně před vykreslením na obrazovku. Při posouvání zásobníky znovu používají již vytvořené pohledy, čímž se vyhýbají novým alokacím — to snižuje zatížení alokátoru paměti a garbage collectoru Swiftu.

Pro práci s línými zásobníky je nutné je umístit do ScrollView — bez posouvání budou prvky přesahující hranice obrazovky jednoduše oříznuty, nikoli líně vytvořeny.

Jak funguje líné načítání

Mechanismus líného načítání v LazyVStack je založen na geometrii: SwiftUI sleduje pozici každého podřízeného pohledu vůči kontejneru ScrollView. Když prvek překročí hranici viditelné oblasti (s malým bufferem několika bodů), systém zavolá jeho inicializátor a vykreslí obsah. Když prvek opustí obrazovku, SwiftUI pohled zničí, ale zachová stav přes @State, pokud je označen jako uchovatelný.

Tento přístup se liší od VStack, kde jsou všechny podřízené pohledy vytvořeny okamžitě při inicializaci kontejneru, bez ohledu na jejich viditelnost. Pro seznam 10 000 prvků VStack vytvoří 10 000 instancí pohledů v paměti, zatímco LazyVStack — pouze ty, které se vejdou na obrazovku (obvykle 8–15).

Velikosti a zarovnání

LazyVStack přijímá tři konfigurační parametry: alignment (HorizontalAlignment — leading, center, trailing), spacing (CGFloat — mezera mezi prvky) a pinnedViews (PinnedScrollableViews — připínání záhlaví sekcí). LazyHStack používá stejné parametry, ale alignment přijímá VerticalAlignment (top, center, bottom).

Rozdíly mezi LazyVStack a VStack: výkon a paměť

Hlavní rozdíl mezi LazyVStack a VStack — strategie vytváření podřízených prvků. VStack (eager stack) vypočítává velikost a pozici všech podřízených pohledů v okamžiku vykreslení, což ho činí nevhodným pro velké dynamické seznamy. LazyVStack (líný zásobník) odkládá vytvoření až do okamžiku, kdy se prvek stane viditelným.

Porovnejme chování na příkladu seznamu 1000 řádků textu. VStack načte všech 1000 řádků do paměti okamžitě, zavolá inicializátor každého řádku a přidělí mu paměť. To vede k poklesu výkonu na slabších zařízeních (iPhone SE, iPad mini) a prodloužení doby spouštění obrazovky. LazyVStack načte pouze viditelných 10–12 řádků a zbytek vytvoří během posouvání.

Praktický test (použití Xcode Instruments, profil Allocations) ukazuje: na iPhone 12 mini spotřebuje seznam 5000 prvků s LazyVStack 3–5 MB paměti, zatímco VStack se stejným obsahem — 150–250 MB, což je 50krát více. Počáteční doba vykreslení pro LazyVStack je ~50 ms oproti ~800 ms pro VStack na stejném zařízení.

Vyberte VStack pro statické nebo krátké seznamy (do 10–15 prvků) a LazyVStack — pro jakékoli dynamické nebo potenciálně dlouhé seznamy. Apple doporučuje používat LazyVStack ve výchozím nastavení, pokud si nejste jisti maximální velikostí seznamu.

Kdy je VStack stále potřeba

VStack zůstává nejlepší volbou pro statická rozhraní: obrazovka profilu, přihlašovací formulář, karta produktu — kde je počet prvků známý a nepřesahuje 10–15. VStack pracuje rychleji při prvním vykreslení takového počtu prvků, protože neplýtvá prostředky na sledování geometrie a líné načítání. Kromě toho VStack funguje správně mimo ScrollView (např. uvnitř ZStack nebo Group), zatímco LazyVStack bez ScrollView ztrácí smysl.

Kdy použít LazyVStack a LazyHStack

Líné zásobníky jsou optimální pro scénáře s velkým nebo nepředvídatelným počtem prvků: feedy sociálních sítí, katalogy produktů, seznamy chatů, knihovny mediálních souborů, deníky událostí, administrační panely s tisíci záznamy.

Konkrétní případy použití: seznam zpráv v messengeru (desítky tisíc zpráv), karusel obrázků v aplikaci galerie, zpravodajský feed s nekonečným načítáním, seznam objednávek v internetovém obchodě. LazyHStack je zvláště užitečný pro horizontální karusely — jako Stories v Instagramu nebo reklamní bannery.

Kontraindikace: rozhraní s animací výskytu prvků (líné zásobníky nepodporují přechody mezi stavy mazání prvků bez dodatečné logiky), případy kdy všechny prvky mají být viditelné současně (krátký seznam zaškrtávacích polí) a když potřebujete přesnou kontrolu nad opětovným použitím buněk (v tomto případě může být vhodnější List nebo Table).

Příklady kódu: seznam, mřížka a sekce

Příklad 1: Jednoduchý vertikální seznam s LazyVStack

Základní příklad zobrazuje 1000 prvků s minimální spotřebou paměti. Klíčové prvky: ScrollView jako kontejner posouvání, LazyVStack pro líné načítání, ForEach s identifikátorem pro iteraci dat.

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

Kód vytvoří ScrollView, uvnitř kterého je umístěn LazyVStack s mezerou 8pt mezi prvky. ForEach prochází polem items a vytváří Text pro každý index. Díky línému načítání je z 1000 prvků současně v paměti pouze viditelných 10–12.

Příklad 2: Sekce s pinnedViews

Příklad demonstruje seskupování prvků do sekcí s připnutými záhlavími, jako v kontaktech iOS. Section definuje záhlaví a obsah, pinnedViews: .sectionHeaders fixuje záhlaví v horní části obrazovky při posouvání.

swift
import SwiftUI

struct SectionedList: View {
    let cities = ["Moskva", "Londýn", "Tokio", "New York", "Paříž"]
    let countries = ["Rusko", "Velká Británie", "Japonsko", "USA", "Francie"]

    var body: some View {
        ScrollView {
            LazyVStack(pinnedViews: .sectionHeaders) {
                Section(header: Text("Města").font(.title).bold()) {
                    ForEach(cities, id: \.self) { city in
                        Text(city).padding(8)
                    }
                }
                Section(header: Text("Země").font(.title).bold()) {
                    ForEach(countries, id: \.self) { country in
                        Text(country).padding(8)
                    }
                }
            }
        }
    }
}

Připnutá záhlaví (.sectionHeaders) se chovají jako section headers v UITableView: při posouvání sekce se záhlaví „přilepí" k hornímu okraji obrazovky, dokud celá sekce nezmizí, poté je nahrazeno záhlavím následující sekce. pinnedViews lze kombinovat: .sectionHeaders a .sectionFooters současně.

Příklad 3: Horizontální karusel s LazyHStack

LazyHStack se používá pro horizontální posouvání — karusely obrázků, horizontální seznamy kategorií. Parametr alignment: .top zarovnává prvky k hornímu okraji.

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

Kód vytvoří horizontální ScrollView s LazyHStack. Ze 100 obdélníků se současně zobrazují 2–3 (v závislosti na šířce obrazovky a velikosti prvků). Při posouvání doleva se nové prvky načítají líně. Výška kontejneru je pevná (220pt), aby se předešlo nekonečné výšce v horizontálním posouvání.

PinnedViews a sekce v líných zásobnících

PinnedScrollableViews — možnost konfigurace LazyVStack a LazyHStack, která spravuje připínání záhlaví a zápatí sekcí při posouvání. Jsou podporovány dvě hodnoty: sectionHeaders (záhlaví se lepí k začátku kontejneru) a sectionFooters (zápatí se lepí ke konci).

Mechanismus pinned views funguje pouze uvnitř kontejneru Section vnořeného do LazyVStack. Každá sekce má záhlaví a/nebo zápatí, které automaticky získávají chování lepení. SwiftUI sleduje pozici každé sekce vůči hranicím ScrollView a přepíná viditelnost připnutého prvku při přechodu mezi sekcemi.

Důležité: pinnedViews zvyšují složitost výpočtu rozvržení, protože SwiftUI musí neustále přepočítávat, které záhlaví je právě připnuto. Používejte pinnedViews pouze když je funkce skutečně potřeba — pro jednoduché seznamy bez sekcí je lepší tento parametr vynechat. Apple ve své dokumentaci (Human Interface Guidelines, 2024) doporučuje použití připnutých záhlaví pro abecední rejstříky a seskupování podle data.

Tipy pro optimalizaci líných zásobníků

Správné používání identifikátorů — nejdůležitější faktor výkonu LazyVStack. Každý prvek ve ForEach musí mít stabilní jedinečné id. Použití \.self s primitivními typy (Int, String) je přípustné, ale pro datové modely vždy implementujte protokol Identifiable. Nestabilní id (např. UUID generované pokaždé) nutí SwiftUI znovu vytvářet všechny pohledy při každé aktualizaci.

Vyhněte se těžkým výpočtům uvnitř body každého prvku zásobníku. Pokud prvek obsahuje složité rozvržení nebo zpracování dat — přesuňte logiku do samostatné struktury pohledu s vlastním líným načítáním. Používejte EquatableView k zabránění zbytečnému překreslování, když se data prvku nezměnila.

Pro obrázky uvnitř LazyVStack bezpodmínečně aplikujte asynchronní načítání (AsyncImage) nebo cachování přes Kingfisher/Nuke. Každý prvek objevující se na obrazovce by neměl synchronně načítat obrázek — to způsobí zasekávání při posouvání (jank). Podle údajů WWDC Session 10031 je optimální velikost bufferu pro prefetching 3–5 obrazovek vpřed a vzad od aktuální pozice.

Měřte výkon přes Xcode Instruments s profilem SwiftUI. Věnujte pozornost metrikám: počet vyhodnocení body, alokace a snímková frekvence (FPS). Cílové hodnoty: FPS > 55 při posouvání, doba vykreslení jednoho prvku < 1 ms.

Často kladené otázky

Jaký je rozdíl mezi LazyVStack a List ve SwiftUI?

List poskytuje vestavěné možnosti: úpravy přejetím (swipeActions), mazání přes .onDelete, přesouvání přes .onMove, styl seskupování .insetGrouped. LazyVStack je nástroj nižší úrovně bez vestavěné podpory gest úprav. List interně používá LazyVStack, ale přidává nativní styl tabulky iOS. Pokud potřebujete vlastní design buňky a nepotřebujete vestavěné úpravy — zvolte LazyVStack. Pokud potřebujete swipeActions, .onDelete a práci s @FetchRequest — použijte List.

Proč LazyVStack vytváří prvky, které nejsou viditelné na obrazovce?

Líné zásobníky používají prefetching — SwiftUI vytváří prvky s mírným předstihem (prefetch buffer), aby bylo posouvání plynulé. Velikost bufferu se automaticky přizpůsobuje rychlosti posouvání a výkonu zařízení. Podle údajů profilování Apple, prefetch buffer obvykle činí 1–3 obrazovky ve směru posouvání. Pokud vidíte, že se vytváří příliš mnoho neviditelných prvků, zkontrolujte, zda nemáte identifikátory generované pokaždé nebo těžké výpočty v inicializátoru pohledu.

Lze vnořit LazyVStack do VStack nebo naopak?

Ano, ale s omezeními. Vnoření LazyVStack do VStack je nesmyslné — vnější VStack okamžitě vytvoří všechny prvky vnitřního LazyVStack a zruší líné načítání. Vnoření VStack do LazyVStack je povoleno a neničí líný mechanismus. Vnoření LazyVStack do jiného LazyVStack je přijatelné pro vnořené sekce, ale sledujte výkon: každá úroveň přidává režii na sledování geometrie.

Jak přidat oddělovače mezi prvky LazyVStack?

SwiftUI neposkytuje vestavěné oddělovače pro LazyVStack. Přidejte je ručně: umístěte Divider() za každý prvek ve ForEach, nebo použijte modifikátor .overlay(Divider(), alignment: .bottom) na každém prvku. Pro vlastní oddělovače kreslete Rectangle().frame(height: 1).foregroundColor(.gray.opacity(0.3)).

Shrnutí

  • LazyVStack — vertikální líný zásobník SwiftUI pro efektivní vykreslování velkých seznamů s vytvářením prvků při vstupu do oblasti viditelnosti.
  • LazyHStack — horizontální protějšek pro karusely a horizontální seznamy se stejným mechanismem líného načítání.
  • Rozdíl od VStack — VStack vytváří všechny prvky okamžitě; líné zásobníky pouze viditelné, šetří až 50× paměti na velkých sadách.
  • pinnedViews — připínání záhlaví sekcí přes Section { header: }.sectionHeaders pro efekt „přilepení".
  • Identifikace přes id — povinná podmínka: ForEach vyžaduje jedinečné stabilní id pro každý prvek.
  • Výkon — prefetch buffer 1–3 obrazovky, EquatableView k zabránění zbytečnému překreslování, asynchronní načítání obrázků.
  • Volba nástroje — LazyVStack pro vlastní návrhy, List pro vestavěné úpravy a swipeActions.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také