Does LazyVStack and LazyVGrid release views from memory inside a ScrollView?

I am using LazyVStack inside a ScrollView. I understand that lazy views are rendered only when they come into view. However, I haven’t heard much about memory deallocation.

I observed that in iOS 18 and later, when scrolling up, the bottom-most views are deallocated from memory, whereas in iOS 17, they are not (Example 1).

Additionally, I noticed a similar behavior when switching views using a switch. When switching views by pressing a button, the view was intermittently deinitialized. (Example 2).

Example 1)

struct ContentView: View {
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(0..<40) { index in
                    CellView(index: index)
                }
            }
        }
        .padding()
    }
}

struct CellView: View {
    let index: Int
    @StateObject var viewModel = CellViewModel()
    var body: some View {
        Rectangle()
            .fill(Color.accentColor)
            .frame(width: 300, height: 300)
            .overlay {
                Text("\(index)")
            }
            .onAppear {
                viewModel.index = index
            }
    }
}

class CellViewModel: ObservableObject {
    @Published var index = 0
    
    init() {
        print("init")
    }
    
    deinit {
        print("\(index) deinit")
    }
}


#Preview {
    ContentView()
}

Example 2


struct ContentView: View {
    @State var index = 0
    var body: some View {
        LazyVStack {
            Button(action: {
                if index > 5 {
                 index = 0
                } else {
                    index += 1
                }
            }) {
                Text("plus index")
            }
            
            MidCellView(index: index)
        }
        .padding()
    }
}

struct MidCellView: View {
    let index: Int
    var body: some View {
        switch index {
        case 1:
            CellView(index: 1)
        case 2:
            CellView(index: 2)
        case 3:
            CellView(index: 3)
        case 4:
            CellView(index: 4)
        default:
            CellView(index: 0)
        }
    }
}

struct CellView: View {
    let index: Int
    @StateObject var viewModel = CellViewModel()
    var body: some View {
        Rectangle()
            .fill(Color.accentColor)
            .frame(width: 300, height: 300)
            .overlay {
                Text("\(index)")
            }
            .onAppear {
                viewModel.index = index
            }
    }
}

class CellViewModel: ObservableObject {
    @Published var index = 0
    
    init() {
        print("init")
    }
    
    deinit {
        print("\(index) deinit")
    }
}
--------------------
init
init
init
init
init
2 deinit
3 deinit
4 deinit
init
Answered by DTS Engineer in 826573022

Correct, Lazy Stack, manages memory footprint by only loading views incrementally as it becomes visible.

Keep in mind that onAppear/onDisappear do not equate visibility but when a view is added or removed from the view hierarchy.

Accepted Answer

Correct, Lazy Stack, manages memory footprint by only loading views incrementally as it becomes visible.

Keep in mind that onAppear/onDisappear do not equate visibility but when a view is added or removed from the view hierarchy.

@DTS Engineer , but the question was:

Does LazyVStack and LazyVGrid release views from memory inside a ScrollView?

in iOS 18

And not about loading views incrementally.

With such a big change, why not make it a separate component? Or at least add a parameter to existing? For example, so it looks like this:

ReusableVStack { ... }
// or
LazyVStack(reuseViews: true) { ... }
Does LazyVStack and LazyVGrid release views from memory inside a ScrollView?
 
 
Q