LazyVStack not refreshing content size correctly for child list container view in SwiftUI

If initially content/list is large and scrolled to bottom, second time scroll bar offset remains at same place for small reloaded content making contents invisible(as small contents are at top of list). Only after scrolling list to the top small list contents becomes visible else its hidden.

If I replace LazyVStack to VStack , issue goes aways but I won't be able to make sticky sections in it.

To Reproduce this issue:

  1. Click Click me button to reload long list
  2. Scroll to bottom
  3. Click Click me button to reload small list
  4. Small list contents are invisible
  5. Now scroll to top and then it shows small list and adjusts list content size

Output:

Sample Code:

//  ContentView.swift
//  LazyVStackIssue
//  Created by Vishwanath Deshmukh on 12/24/21.

import SwiftUI

struct ContentView: View {
   @State var array = ["Vish"]
   @State var count = 1

   var body: some View {

       ZStack(alignment: .top) {
           VStack(spacing: 0) {

               Button(action: refreshData) {
                   Text("Click me")
               }

               ScrollView(.vertical) {

                   LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {

                       VStack(spacing: 20) {

                           // Dynamic Optional section
                           Section(header: Text("Header"), content: {
                               Text("Contents..")
                           })

                           // Dynamic view 2
                           VStack(spacing: 10) {

                               ForEach(array, id: \.self) { itemDataModel in
                                   Text(itemDataModel)
                                       .background(Color.red)
                                       .frame(height: 50)
                               }
                           }
                       }
                   }
               }
           }
       }
   }

   private func refreshData() {
       count += 1

       if count % 2 == 0 {

           array = ["Vish1", "Vish2", "Vish3", "Vish4", "Vish5", "Vish6", "Vish7", "Vish8", "Vish9", "Vish10", "Vish11", "Vish12", "Vish13", "Vish14", "Vish111", "Vish211", "Vish113", "Vish114", "Vish115", "Vish116", "Vish117", "Vish118", "Vish119", "Vish1110", "Vish1111", "Vish1112", "Vish1113", "Vish1114"  ]

       } else {
           array = ["Vish11", "Vish12", "Vish13", "Vish14", "Vish15"]
       }
   }

}



struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
       ContentView()
   }
}```

I don't see pinned headers with your version. I believe Section needs to be a direct child of VStack to work:

               LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
                   Section(header: Text("Header")) {
                       ForEach(array, id: \.self) { itemDataModel in
                           Text(itemDataModel)
                               .background(Color.red)
                               .frame(height: 50)
                       }
                   }
               }

This also fixes the scrolling issue you encounter.

Hi @tera, yes that's true. In my above example pinned section is optional and that's works correctly. However, second dynamic view component is getting returned from some other runtime View file wrapped within a VStack container. If I remove VStack above ForEach and add ForEach(array) as direct element of LazyVStack then it works but not with a VStack wrapper.

Second dynamic view component could be anything and it could be wrapped within any container view like VStack or HStack.

e.g.

 // Dynamic view 2
                           VStack(spacing: 10) {
                               Text("Title for this list component")
                               VStack {
                                 ForEach(array, id: \.self) { itemDataModel in
                                     Text(itemDataModel)
                                         .background(Color.red)
                                         .frame(height: 50)
                                 }
                               }
                              Text("Some footer for above list")
                           }
                           .background(Color.somebackground)

I am not sure if there is any workaround to get that with LazyVStack, since as shown in output image after scrolling to Top, LazyVStack show child list correctly.

Do the id trick:

                   LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
                        ...
                   }
                   .id(count)
1 Like

Marvelous, thank you so much @tera. Assigning unique ID to LazyVStack worked for fixing content size correctly. You saved my holidays. :sweat_smile:

Moderators, it might be worth to make a section SwiftUI in related-projects/, because eventually SwiftUI is UI library + Render packet, which is not very well documented at the moment and needs some clarification of some concepts

1 Like