こんにちは。ymurao2です!
ScrollView + LazyVStackで縦に長い画面を実装しているとき、VStackとLazyVStackの使い分けについて悩むことが少なからずあったので、調査しました
動作環境
Xcode15.0.1
調査コード
ScrollView {
+ // ここと
LazyVStack {
+ // ここと
LazyVStack {
ForEach(0..<25, id: \.self) { i in
Text(String(i))
.padding()
.onAppear {
print("upper:" + String(i))
}
}
}
.background(Color.gray)
+ // ここと
LazyVStack {
ForEach(25..<50, id: \.self) { i in
Text(String(i))
.padding()
.onAppear {
print("middle:" + String(i))
}
}
}
.background(Color.red)
+ // ここをいじります
LazyVStack {
ForEach(50..<75, id: \.self) { i in
Text(String(i))
.padding()
.onAppear {
print("lower:" + String(i))
}
}
}
.background(Color.blue)
}
}
1.LavyVStack + LazyVStack
ScrollView {
LazyVStack {
LazyVStack {
// 省略
}
.background(Color.gray)
LazyVStack {
// 省略
}
.background(Color.red)
LazyVStack {
// 省略
}
.background(Color.blue)
}
}
Viewが表示される前にonAppearは発火しているので、ログは実際に表示されるタイミングより早く表示されています
Stack Overflow参照
このパターンの場合、全てのViewが遅延読み込みされていました
2.LazyVStack + VStack
// 最初のコードとのdiff
ScrollView {
LazyVStack {
+ VStack {
// 省略
}
.background(Color.gray)
+ VStack {
// 省略
}
.background(Color.red)
+ VStack {
// 省略
}
.background(Color.blue)
}
}
このパターンの場合、VStackの先頭がレンダリングされるときに、VStackの内のonAppearが全て発火していました
3.VStack + LazyVStack
// 最初のコードとのdiff
ScrollView {
+ VStack {
LazyVStack {
// 省略
}
.background(Color.gray)
LazyVStack {
// 省略
}
.background(Color.red)
LazyVStack {
// 省略
}
.background(Color.blue)
}
}
1.のLavyVStack + LazyVStackと同じ結果になりました
このパターンも良さそうに見えましたが、別の問題がありました
// 最初のコードとのdiff
ScrollView {
VStack {
LazyVStack {
// 省略
}
.background(Color.gray)
LazyVStack {
// 省略
}
.background(Color.red)
LazyVStack {
// 省略
}
.background(Color.blue)
+ Text("表示されたぞ!!!")
+ .onAppear {
+ print("表示されたぞ!!!")
+ }
}
}

一番下にTextを差し込んだところ、こちらは表示される前にonAppearが発火しました
これは期待通りの動作とは言えなさそうです
4.VStack + VStack
// 最初のコードとのdiff
ScrollView {
+ VStack {
+ VStack {
// 省略
}
.background(Color.gray)
+ VStack {
// 省略
}
.background(Color.red)
+ VStack {
// 省略
}
.background(Color.blue)
}
}

Gifだと分かりにくいですが、全てのViewのonAppearが一度に発火していました
まとめ
| 実装方法 | 結果 | 評価 |
|---|---|---|
LavyVStack + LazyVStack
|
全てのViewが遅延読み込みされた |
◎ |
LazyVStack + VStack
|
VStack内のViewのonAppearが同時に発火 |
× |
LazyVStack + VStack
|
VStack内の要素が全てVStackに入っていれば問題なさそう |
△ |
VStack + VStack
|
全てのViewが遅延読み込みされない |
× |
調査の結果、LazyVStack + LazyVStackで実装するのが良さそうに見えました
この記事がお役に立てば、ぜひいいね👍お願いします!
参考文献
LazyVStackを使用すると描画処理が何度も走ってしまう
lazyvstack-row-onappear-is-called-early



