It's certainly an interesting case. I tried out a number of variations in an attempt to get a better understanding. Here are some observations:
If a VStack
is used instead of LazyVStack
then the colors are preserved. This is perhaps no big surprise because you would expect a VStack
to load the nested content once and then hold it in memory.
If you change the implementation of MyView
to this:
struct MyView: View {
let color = Color.random
var body: some View {
color
}
}
...then you find that the colors are preserved. So it is not the view instances themselves which are being discarded, but the views returned by their body
property.
- To confirm the last point, you can add an initializor to
MyView
and print the index (as was suggested in one of the comments to the question):
init(_ index: Int, _ item: Int) {
print("creating \(index)-\(item)")
}
This shows that the views are only created once.
- I added some buttons to make scrolling quicker:
ScrollViewReader { proxy in
ScrollView(.vertical) {
LazyVStack {
Button("Scroll to bottom") {
withAnimation(.easeInOut(duration: 10)) { proxy.scrollTo(-2) }
}
.id(-1)
.buttonStyle(.borderedProminent)
// has has many items so you can scroll
ForEach((0...40), id: \.self) { index in
MyView(index, 1) // (1)
VStack {
MyView(index, 2) // (2)
}
}
Button("Scroll to top") {
withAnimation(.easeInOut(duration: 10)) { proxy.scrollTo(-1) }
}
.id(-2)
.buttonStyle(.borderedProminent)
}
}
}
Then I tried steadily doubling the number of rows, trying to see if there was a point at which the views were being re-created (meaning, they were no longer being cached). I thought that this might cause the colors of view 1
to change, sooner or later. If you scroll up and down many times then what you find is that it steadily fills the gaps of the rows that were not created the first time, but it is difficult to tell if any rows are actually being re-created. My guess is that the cache is optimized, so anything that was recently shown is not likely to be discarded. I certainly wasn't able to see any changes in the rows I was monitoring.
Conclusions from all of this
It is not surprising to find that the content of the LazyVStack
is being discarded when it is out of view. What is perhaps more surprising, is that all cases of view 1
preserve their color. But the experiments above have shown that in fact it is only the body of the nested views (view 2
) that are being discarded, probably as a memory optimization. If the random color is captured in init
instead of being chosen in body
then these views do not change color either.