0

I have code, where a LazyVGrid inside a Form is not rendered correctly the first time. After some scrolling, it renders correctly.

this is iOS 16.

My problem can be reproduced with this code. Should you try it, please use an iPhone(emulator) to restrict the width.

import SwiftUI
struct ContentView: View {
    var body: some View {
        Form {
            Section(header: Text("Section 1")) {
                CustomVGrid()
            }

            Section(header: Text("Section 2")) {
                CustomVGrid()
            }
        }
    }
}

struct CustomVGrid: View {
    let layout = [GridItem(.adaptive(minimum: 350, maximum: 500), spacing: 20)]

    var body: some View {
        LazyVGrid(columns: layout, alignment: .leading, spacing: 20) {
            Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
                //.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
                .background(Color.green)
            ForEach(0..<20, id: \.self) { index in
                VStack {
                    Text("\(index): Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,  ")
                        .background(Color.red)
                    Spacer()
                }
            }
        }
    }
}

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

When starting the very first time, the code renders like this:

Too much space

You see the unintended large white space above the first green cell.

When scrolling down and up again, it renders correctly:

enter image description here

You see no unintended white space above the green cell.

How do I make it render correctly the first time, without scrolling? (rotating and rotating back fixes the problem, too)

Gerd Castan
  • 6,275
  • 3
  • 44
  • 89

1 Answers1

1

A lot of times the workaround for these layout bugs can be changing the view at .onAppear(). LazyVGrid seems to like a definite height when it is first laying out, then it is fine with anything after. This is ugly, but it seems to fix your problem:

struct CustomVGrid: View {
    let layout = [GridItem(.adaptive(minimum: 350, maximum: 500), spacing: 20)]
    // Set a var to trigger the height change
    @State var changeHeight: Bool = true
    
    var body: some View {
        LazyVGrid(columns: layout, alignment: .leading, spacing: 20) {
            Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
                // Use the bool to trigger the height change.
                .frame(maxHeight: changeHeight ? 100 : .infinity)
                .background(Color.green)
            ForEach(0..<20, id: \.self) { index in
                VStack {
                    Text("\(index): Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,  ")
                        .background(Color.red)
                    Spacer()
                }
            }
        }
        // In onAppear, trigger the change
        .onAppear {
            changeHeight.toggle()
        }
    }
}
Yrb
  • 8,103
  • 2
  • 14
  • 44
  • Works. Thanks. I feel pain clicking on accepted, but you deserve the points :-) – Gerd Castan Jun 29 '23 at 18:22
  • Thanks for this trick. Shouldn’t you set the changeHeight value to false to prevent it can get back to true upon pushing/poping a view because onAppear called another time? – Ptit Xav Jun 29 '23 at 18:34
  • If the view goes out of the hierarchy, you are starting from scratch, so it should restart the whole thing. This can be more robust with a ViewModel. I wasn't particularly trying to make production code here... – Yrb Jun 29 '23 at 20:18