DMBot/WWDC26/SwiftUI
#WWDC26#SwiftUI#scrolling Published: 2026-06-10 · Video ID: 4FNbTRr5MtQ · 21 min

Lazy stacks 不是只省記憶體:
它們會用估算與預取換取捲動流暢度

這支 session 的重點不是「用 LazyVStack 取代 VStack」而已,而是理解 SwiftUI 為什麼會估算 off-screen view 的尺寸、何時會提前 evaluate body、以及哪些寫法會讓 programmatic scrolling 和 state 保存變得不穩。

Apple Developer · WWDC26 · Dive into lazy stacks and scrolling with SwiftUI YouTube source
Core primitive
LazyVStack / HStack
只載入可見與即將可見的子視圖
Main tradeoff
Estimated size
off-screen 高度與 offset 不是穩定真值
Performance lever
Prefetch
把 expensive work 拆到多個 frame
Risk area
Dynamic children
條件式 leaf body 會拉長 view 生命週期
// CHAPTERS
00:00Introduction:lazy stacks 的價值與前提知識
01:24Layout:可見範圍、估算高度與 ScrollView 協調
09:13Subview loading:view struct 不一定等於一個 subview
13:15Prefetching:提前 evaluate / layout,避免 dropped frame
17:40Programmatic scrolling:scrollTo off-screen view 的限制
19:55Next steps:實務守則總結

一句話結論

Lazy stack 的核心契約是:SwiftUI 不需要一次建立所有子視圖,但因此必須估算還沒載入的內容尺寸。你可以把它想成一個只看局部資料的 layout engine;它很快,但最怕「view body 裡才決定資料是否存在」和「view 出現後才大幅改變尺寸」。

如果你的捲動定位、內容高度或重要狀態依賴 off-screen view 的實際存在,lazy stack 會把這些假設暴露成 hitch、offset 跳動或狀態遺失。

Layout:尺寸是估算,不是帳本

LazyVStack 只把可見 rect 填滿,離開畫面的子視圖會被移出 lazy stack。為了讓 ScrollView 知道整段內容大約有多長,它會用已放置 view 的平均尺寸與剩餘數量估算完整 content size。當後續載入的 view 尺寸與估算不同,content size 和 offset 也會修正。

實務上要避免用 lazy stack 的 absolute content size 或 content offset 做精準邏輯。這些值會隨著新的 view 被量測而變動,拿它們當穩定座標系,最容易在長列表或跳轉時出現微妙錯位。

Subview loading:不要在 leaf body 做資料過濾

影片最值得記下的是:你寫的一個 view struct 不一定對應 lazy stack 看到的一個 subview。若 StepView 的 body 在頂層回傳兩個 view,lazy stack 可能分別載入它們;若 body 依環境值決定回傳一個或零個 view,lazy stack 為了維持 index 可能保留更早的 view。

這代表在 ForEach 的 leaf view 裡用條件式內容來「過濾資料」不是好主意。更好的做法是在資料層先過濾,例如 SwiftData 用 Predicate,讓 lazy stack 一開始就知道有幾個子視圖,不必先建 view 才知道 count 與 index。

Prefetch:onAppear 不是初始化生命週期

lazy stack 會在捲動方向快到新內容時提前做部分工作,例如 evaluate body 或 layout,讓真正進入畫面時不用在單一 frame 內完成所有 expensive work。這是 smooth scrolling 的重要來源。

但若你把每個 cell 的 setup 放在 onAppear,而且出現後才改變尺寸或內容,等於讓 prefetch 做好的 work 報廢並重做。可提前準備的資料與合理初始狀態應放在 initializer、model object 或 cache loader;onAppear 比較適合 infinite scrolling 這類「真的進入畫面才觸發」的行為。

Programmatic scrolling:ID 可跳,但布局要穩

SwiftUI 可以 scroll 到還沒在畫面上的 ID,但 lazy stack 仍得估算那個 view 的位置。最順的情況是 ForEach 的每筆資料固定解析成單一 subview,lazy stack 可以快速查 ID 與 count,不用建立大量 view。

相反地,若 leaf view 可能解析成零個或多個 subviews,或透過 onGeometryChange 改 state 再觸發第二次 layout,跳轉位置就更容易不穩。需要複雜尺寸關係時,優先考慮 SwiftUI layout primitives 或 custom layout,而不是讓 view 出現後再回頭改高度。

我會立刻採用的四條規則

01
資料層過濾,不在 cell body 裡 return empty
用 query / predicate / array filtering 決定資料集合,避免 lazy stack 為了 index 保留過多 view。
02
重要狀態放到 model 或外層 binding
view 捲出畫面後 state 可能被刪除;選取、高亮、下載狀態不要只存在 leaf view。
03
讓 view 初始尺寸足夠接近最終尺寸
prefetch 才能幫忙,programmatic scrolling 也比較不會被出現後尺寸變動推歪。
04
不要把 content offset 當精準資料來源
lazy stack 的 off-screen 尺寸會修正,absolute offset 適合觀察,不適合承載核心邏輯。
// TL;DR

來源