SwiftUI ScrollView iOS 26: scrollTransition, scrollPosition és scrollTargetLayout mesterfokon

Lépésről lépésre vezetlek végig az iOS 26 ScrollView újdonságain: scrollTransition fázisok, programozott pozíció, scrollTargetLayout, contentMargins, paginált görgetés és teljesítményoptimalizálás valós kódpéldákkal.

SwiftUI ScrollView iOS 26: Útmutató 2026
On this page
  1. Miért érdemes most újragondolni a ScrollView-t iOS 26 alatt?
  2. 1. Alap ScrollView felépítése iOS 26-ban
  3. 2. scrollTransition: vizuális effektek a görgetés fázisaihoz
  4. 3. scrollPosition: programozott görgetés és pozíció-binding
  5. 4. scrollTargetLayout és scrollTargetBehavior: natív paginálás
  6. 5. contentMargins és safeAreaPadding: tiszta layout-elválasztás
  7. 6. onScrollGeometryChange: pontos görgetésfigyelés
  8. 7. Teljesítményoptimalizálás iOS 26-ban
  9. 8. Hozzáférhetőség: ne hagyd ki a Reduce Motion ellenőrzést
  10. 9. Hibakeresés: gyakori csapdák
  11. 10. Mintapélda: olvasói felület scrollTransition + scrollPosition kombinációval
  12. GYIK — Gyakori kérdések a SwiftUI ScrollView iOS 26 alatt
  13. Összegzés

Bevallom, évekig tartott, mire megbarátkoztam azzal, hogy a SwiftUI ScrollView nem csak egy buta görgethető doboz. Az iOS 17 óta lassú forradalom zajlik a háttérben, és iOS 26 alatt végre érettségit ír olyan modifier-csapat, mint a scrollTransition, a scrollPosition, a scrollTargetLayout és a contentMargins. Ebben az útmutatóban konkrét, 2026-ban is működő mintákon keresztül mutatom meg, hogyan építhetsz olyan görgetési élményt, amilyet korábban csak UIKit-tel és kézzel hegesztett UIScrollViewDelegate implementációval volt esélyed összehozni.

Miért érdemes most újragondolni a ScrollView-t iOS 26 alatt?

A régi ScrollView főleg arra volt jó, hogy kirakja a tartalmat — a pozíció lekérdezéséhez GeometryReader és preferenceKey akrobatika kellett. Őszintén? Ez minden komolyabb projektben fájdalom volt. Az új API-k a következő problémákat oldják meg natívan:

  • Vizuális effektek görgetés közben — egyetlen modifierrel skálázás, fakulás és parallax élmény.
  • Programozott pozícionálás — pontos görgetés egy item ID-ra, edge-re vagy offsetre, kétirányú binding-gal.
  • Pattogás és pagination — natív snap viselkedés a scrollTargetLayout és scrollTargetBehavior párossal.
  • Biztonságos margók — a contentMargins szépen szétválasztja a tartalmi és a görgetősáv-margókat.

A 2026-os iOS 26.x és a Liquid Glass design nyelv mellett ezek az API-k kifejezetten hasznosak. A görgetést érzékelő üveghatások ugyanis csak akkor működnek igazán jól, ha a ScrollView részletesen tudósít a saját fázisairól.

1. Alap ScrollView felépítése iOS 26-ban

Mielőtt belevetnénk magunkat az új modifierekbe, érdemes egy pillanatra megállni az alapoknál. A ScrollView belsejébe tett LazyVStack ma is a leghatékonyabb minta — csak a látható elemeket példányosítja, és az új API-k nagy része is ezen a párosításon keresztül működik a legjobban:

struct FeedView: View {
    let posts: [Post]

    var body: some View {
        ScrollView(.vertical) {
            LazyVStack(spacing: 16) {
                ForEach(posts) { post in
                    PostCard(post: post)
                }
            }
            .scrollTargetLayout()
        }
        .contentMargins(.horizontal, 16, for: .scrollContent)
    }
}

Két dologra figyelj. A scrollTargetLayout() a belső konténerre kerül (nem a ScrollView-ra — ez egy nagyon gyakori melléfogás), a contentMargins pedig a .scrollContent régiót célozza, így a görgetősáv megőrzi a saját margóit.

Mikor használj LazyVStack-et és mikor sima VStack-et?

Ha a tartalom 30 elem alatti, és nincs benne nehéz AsyncImage vagy chart, a sima VStack akár gyorsabb is lehet — a layout nem inkrementális, így nincs külön példányosítási overhead. 50 elem felett viszont (különösen képek mellett) a LazyVStack jelentős memória- és CPU-előnyt ad. Ráadásul item-ID alapú pontos pozíciót csak így kapsz.

2. scrollTransition: vizuális effektek a görgetés fázisaihoz

A scrollTransition(_:axis:transition:) modifier (iOS 17+) a látókörbe lépő és onnan kilépő nézetekre alkalmaz folyamatosan animált effekteket. Az iOS 26-ban a VisualEffect protokoll végre stabilizálódott, és lényegesen több modifier használható benne biztonságosan.

A három transition fázis

A closure egy ScrollTransitionPhase értéket kap. Három állapot létezik:

  • .identity — a nézet teljesen látható, érték: 0.
  • .topLeading — felülről vagy bal szélről közelít, érték: -1.
  • .bottomTrailing — alulra vagy jobb szélre tart, érték: 1.

Klasszikus „halványodj és zsugorodj" effekt

ScrollView {
    LazyVStack(spacing: 20) {
        ForEach(items) { item in
            ItemRow(item: item)
                .scrollTransition(.animated.threshold(.visible(0.3))) { content, phase in
                    content
                        .opacity(phase.isIdentity ? 1 : 0.4)
                        .scaleEffect(phase.isIdentity ? 1 : 0.85)
                        .blur(radius: phase.isIdentity ? 0 : 4)
                }
        }
    }
}

A .threshold(.visible(0.3)) azt jelenti, hogy a transition akkor zárul le, amikor a nézet 30%-a látható. Ez gyakorlatilag elvágja azt a kínos „hirtelen ugrás" érzést a felső és alsó vágódásnál.

Aszimmetrikus transition: csak a felső szélen animálj

Néha tényleg csak az egyik irányban szeretnél effektet — mondjuk a fejlécbe tűnő kártyáknál. A .animated helyett megadhatsz külön topLeading és bottomTrailing viselkedést a fázis vizsgálatával:

.scrollTransition(axis: .vertical) { content, phase in
    content
        .opacity(phase == .topLeading ? 0 : 1)
        .offset(y: phase == .topLeading ? -40 : 0)
}

Pontos vezérlés a phase.value-val

Folyamatos parallax-szerű effekthez közvetlenül a phase.value (-1.0 … 1.0) értékét használd:

.scrollTransition { content, phase in
    content
        .rotation3DEffect(
            .degrees(phase.value * 25),
            axis: (x: 1, y: 0, z: 0)
        )
        .offset(y: phase.value * 30)
}

Fontos megkötés: a scrollTransition closure-ben csak olyan modifier használható, amely nem változtatja meg a tartalom méretét. Tehát nem támogatott pl. a .font() vagy a .padding(). A fordító ezt nem mindig kapja el — csak futás közben fognak ignorálódni a változtatások (én ezen már elvesztegettem egy fél délutánt egy WWDC után, szóval most te is okos lehetsz a károm árán).

3. scrollPosition: programozott görgetés és pozíció-binding

Az iOS 17-ben bevezetett scrollPosition(id:) az iOS 18 óta általánosabbá vált, az iOS 26-ban pedig egy új ScrollPosition érték köré szerveződik, ami egyszerre tárolja az ID-t, az edge-et és az offsetet. Lényegében: végre nem kell három különböző API között zsonglőrködni.

Pozíció figyelése item-ID alapján

struct ChapterReader: View {
    let chapters: [Chapter]
    @State private var visibleChapterId: Chapter.ID?

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 24) {
                ForEach(chapters) { chapter in
                    ChapterView(chapter: chapter)
                        .id(chapter.id)
                }
            }
            .scrollTargetLayout()
        }
        .scrollPosition(id: $visibleChapterId)
        .navigationTitle(visibleChapterId.flatMap { id in
            chapters.first(where: { $0.id == id })?.title
        } ?? "Olvasó")
    }
}

A binding minden olyan görgetésnél frissül, amikor a leadingmost nézet azonosítója megváltozik. Tökéletes a fejléc szinkronban tartására.

Programozott ugrás konkrét elemre

Button("Ugrás az 5. fejezetre") {
    withAnimation(.smooth) {
        visibleChapterId = chapters[4].id
    }
}

Edge és offset alapú pozícionálás

Az iOS 26 alatt a teljes ScrollPosition értéket is használhatod:

@State private var position = ScrollPosition(edge: .top)

ScrollView {
    // ...
}
.scrollPosition($position)

Button("Vissza a tetejére") {
    withAnimation(.snappy) {
        position.scrollTo(edge: .top)
    }
}

A scrollTo(edge:), a scrollTo(id:), és a scrollTo(point:) között a felhasználói szándék szerint válassz — nincs „univerzális helyes" választás, mindegyiknek megvan a maga ideális használata.

4. scrollTargetLayout és scrollTargetBehavior: natív paginálás

Talán a legtöbbet kihagyott trükk a görgetési viselkedés deklaratív leírása. A scrollTargetBehavior(.paging) teljes oldalas pattogást ad, a .viewAligned pedig minden gyermek elemre szépen ráigazodik a görgetés végén.

ScrollView(.horizontal) {
    LazyHStack(spacing: 12) {
        ForEach(stories) { story in
            StoryCard(story: story)
                .frame(width: 280, height: 360)
        }
    }
    .scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.contentMargins(.horizontal, 24, for: .scrollContent)
.scrollIndicators(.hidden)

Ez a kombináció az App Store kártyás listáihoz hasonló élményt ad — körülbelül tíz sor kódból. UIKit-ben ugyanez egyedi UICollectionViewLayout alosztályt igényelt volna, ami (őszintén) önmagában egy hét meló.

Egyéni snap-viselkedés

Ha a .viewAligned nem elég, implementálhatsz saját ScrollTargetBehavior-t:

struct StepSnap: ScrollTargetBehavior {
    let step: CGFloat

    func updateTarget(_ target: inout ScrollTarget, context: TargetContext) {
        let snapped = (target.rect.minY / step).rounded() * step
        target.rect.origin.y = snapped
    }
}

ScrollView { /* ... */ }
    .scrollTargetBehavior(StepSnap(step: 120))

5. contentMargins és safeAreaPadding: tiszta layout-elválasztás

A contentMargins az egyik legfontosabb újdonság, ha pontos elrendezést akarsz. A klasszikus .padding() magát a ScrollView-t (igen, az indikátorokkal együtt) eltolja, ami ronda. A contentMargins ellenben külön kezeli a tartalmi régiót és a görgetősávot:

ScrollView {
    content
}
.contentMargins(.top, 12, for: .scrollContent)
.contentMargins(.top, 0, for: .scrollIndicators)

Ezzel a tartalom 12 ponttal lejjebb kezdődik, de a görgetősáv pontosan a felső szélnél indul. Apró trükk, de a végeredményen sokat dob.

6. onScrollGeometryChange: pontos görgetésfigyelés

Az iOS 18-ban bevezetett onScrollGeometryChange(for:of:action:) modifierrel pontos offset, méret vagy láthatóság alapján reagálhatsz görgetésre — anélkül, hogy a GeometryReader drága frame-mérését használnád:

ScrollView {
    content
}
.onScrollGeometryChange(for: Bool.self) { geometry in
    geometry.contentOffset.y > 80
} action: { _, isPastThreshold in
    withAnimation(.snappy) {
        showCondensedHeader = isPastThreshold
    }
}

A trükk itt: a for: paraméter egy Equatable származtatott érték, így az action closure csak akkor fut le, ha a számított érték ténylegesen változott. Ez óriási teljesítményelőny minden frame-rendelte futtatáshoz képest — különösen ProMotion eszközökön számít.

7. Teljesítményoptimalizálás iOS 26-ban

A görgetés a leggyakoribb felhasználói művelet — a 120 Hz-es ProMotion kijelzők (8.3 ms / frame) miatt minden milliszekundum számít. Pár olyan dolog, amit én is rendre elfelejtek:

  • Részesítsd előnyben a transzformokat a layout helyett. A .scaleEffect() és az .opacity() a fő szálon kívül fut, míg a .padding() új layoutot kényszerít.
  • Stabil id a ForEach-ben — anélkül a SwiftUI minden görgetésnél újraépíti a struktúrát. Itt fordult már elő, hogy egy „véletlen" UUID() hívás 60 fps-ről 28-ra rántott le egy listát.
  • Drawing group nehéz Canvas-hoz — a .drawingGroup() Metal-alapú renderelést kényszerít.
  • onScrollGeometryChange > preferenceKey — a deklaratív API maximum egyszer fut frame-enként.
  • Animáció scope-olása — kerüld a .animation(.default) globális használatát; használj inkább .animation(.smooth, value: state) formát.

8. Hozzáférhetőség: ne hagyd ki a Reduce Motion ellenőrzést

A scrollTransition alapból akkor is animál, ha a felhasználó kikapcsolta a mozgáshatásokat. Tartsd tiszteletben a beállítást — nem opcionális:

@Environment(\.accessibilityReduceMotion) private var reduceMotion

ScrollView {
    LazyVStack {
        ForEach(items) { item in
            ItemRow(item: item)
                .scrollTransition { content, phase in
                    content
                        .opacity(reduceMotion ? 1 : (phase.isIdentity ? 1 : 0.5))
                        .scaleEffect(reduceMotion ? 1 : (phase.isIdentity ? 1 : 0.92))
                }
        }
    }
}

9. Hibakeresés: gyakori csapdák

  • scrollPosition nem frissül — biztosan rátetted a scrollTargetLayout()-ot a belső LazyVStack-re? A ScrollView-on nem fog működni. (Ezt szinte mindenkinél láttam, aki most kezdi.)
  • scrollTransition villog — a closure-ben olyan modifier van, ami a méretet befolyásolja (pl. padding). Cseréld scaleEffect-re.
  • contentMargins nem látszik — meggyőződtél-e róla, hogy nem .frame-et adsz a ScrollView-ra? A frame felülírja a margók hatását.
  • Lassú görgetés AsyncImage-zsel — explicit cache vagy onAppear alapú prefetch kell; az iOS 26 nem cache-eli alapból.

10. Mintapélda: olvasói felület scrollTransition + scrollPosition kombinációval

Na, akkor rakjuk össze az egészet. Az alábbi minta kombinálja a fázis-alapú effekteket, a programozott pozíciót és a görgetésfigyelést — gyakorlatilag egy kerek olvasói nézet alapja:

struct ArticleReader: View {
    let blocks: [ContentBlock]
    @State private var position = ScrollPosition(edge: .top)
    @State private var showJumpToTop = false

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 16) {
                ForEach(blocks) { block in
                    BlockView(block: block)
                        .scrollTransition(.animated.threshold(.visible(0.2))) { content, phase in
                            content
                                .opacity(phase.isIdentity ? 1 : 0.5)
                                .blur(radius: phase.isIdentity ? 0 : 6)
                        }
                }
            }
            .scrollTargetLayout()
        }
        .scrollPosition($position)
        .contentMargins(.horizontal, 20, for: .scrollContent)
        .onScrollGeometryChange(for: Bool.self) { geo in
            geo.contentOffset.y > 600
        } action: { _, isFar in
            withAnimation(.snappy) { showJumpToTop = isFar }
        }
        .overlay(alignment: .bottomTrailing) {
            if showJumpToTop {
                Button {
                    withAnimation(.smooth) {
                        position.scrollTo(edge: .top)
                    }
                } label: {
                    Image(systemName: "arrow.up")
                        .font(.title2)
                        .padding(14)
                        .background(.ultraThinMaterial, in: .circle)
                }
                .padding(20)
                .transition(.scale.combined(with: .opacity))
            }
        }
    }
}

GYIK — Gyakori kérdések a SwiftUI ScrollView iOS 26 alatt

Mit ad az iOS 26 ScrollView az iOS 17/18-hoz képest?

Az iOS 26 stabilizálja a VisualEffect protokollt, kibővíti a ScrollPosition értéket (egységes ID/edge/offset kezelés), és bevezeti a @Animatable makrót, ami a scroll-vezérelt egyedi animációknál sokat lefarag a boilerplate-ből. Teljesítmény oldalon az onScrollGeometryChange opcionális egyszer-frame koaleszcenciát kapott.

Hogyan érek el sima paginálást SwiftUI-ban a TabView nélkül?

Tedd a tartalmat egy LazyHStack-be a ScrollView-on belül, alkalmazz scrollTargetLayout()-ot a stack-re, majd a ScrollView-ra scrollTargetBehavior(.paging)-et. Ez a TabView-nál rugalmasabb (nem csak teljes oldalakra korlátozódik), és minden iOS 17+ készüléken működik.

Miért nem működik a scrollTransition padding modifierrel?

A scrollTransition closure csak olyan effekteket fogad, amelyek nem módosítják a layoutot — ez a VisualEffect protokoll megkötése. A padding, a frame, vagy a font változtatása újrarendezné a tartalmat, ami megakadná a görgetést. Cseréld scaleEffect, offset vagy opacity modifierre.

Hogyan tartom szinkronban a fejlécet a látható elemmel?

Használj scrollPosition(id:)-t egy State bindingra, és a fejlécben olvasd ki ezt az ID-t. A binding automatikusan frissül a leadingmost nézet váltásakor. Ha a fejléc-frissítés „ugrik", csomagold withAnimation(.smooth)-ba.

Mikor használjak GeometryReadert ScrollView-on belül 2026-ban?

Szinte soha. Az onScrollGeometryChange, scrollPosition és scrollTransition trió 95%-os lefedettséget ad az új projektekben. GeometryReader csak akkor indokolt, ha egyedi méretű háttérréteget kell pontosan a ScrollView tartalmához igazítani — például egy parallax kép esetén, ami a teljes tartalmi régiót takarja.

Összegzés

Az iOS 26 SwiftUI ScrollView API-jai elhozták azt a kifejezőerőt, amelyre évek óta vártunk. A négy modifier — a scrollTransition, a scrollPosition, a scrollTargetLayout és a contentMargins — együtt használva olyan görgetési élményt ad, amilyet korábban csak custom UIKit kóddal lehetett elérni.

Az én javaslatom: kezdj a LazyVStack + scrollTargetLayout alapokkal, építsd rá a pozíció-bindingot, majd finomítsd a scrollTransition effekteket. És kérlek — tényleg kérlek — tartsd tiszteletben az accessibilityReduceMotion beállítást. Az effekt, ami valakit szédít, nem cool, csak rossz UX.