Home > Mobile >  SwiftUI: LazyVStack that contains items of different heights does not work well with ScrollView (mac
SwiftUI: LazyVStack that contains items of different heights does not work well with ScrollView (mac

Time:12-23

The problem

When I populate a LazyVStack with items of varying heights, the ScrollView around it does not behave as expected on macOS 13. This simplified example is still somewhat usable, but as you can see in the screen recording (I'm scrolling very slowly), the scroll position jumps around. In my production app this behavior gets much worse, making it sometimes impossible to scroll upwards again.

enter image description here

Code

struct ContentView: View {
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(1...40, id: \.self) { value in
                    Item(value: value)
                        .id(value)
                        .padding()
                }
            }
            .padding()
        }
        .frame(width: 300, height: 300)
    }
}

struct Item: View {
    let value: Int
    let lorem = "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."

    var body: some View {
        if value < 20 {
            Text("\(value) A long text: \(lorem)")
        }
        else {
            Text("\(value) A short text")
        }
    }
}

iOS vs. macOS

I have found this post regarding the same issue on iOS. I also ran my code on iOS and while the scroll indicator changes its height (which would indicate a change in content size), the scrolling was smooth without jumps of the scroll position.

Question

Has anyone had the same issue and found a way to make this work? The example would easily work with a regular VStack, but I need to use LazyVStack for performance reasons and the ability to add a sticky header.

CodePudding user response:

From what I was able to find, this still seems to be a bug.

I was able to put together a solution where it does not jump around, but you will probably need to adjust the content to be spaced out correct. I did a quick adjustment with negative padding, but some more work will need to be done to have it styled like you had previously.

I think the issue comes from the frame applied to your ScrollView. For some reason the LazyVStack does not like that. Try just removing the frame on the ScrollView - the jumping goes away.

It looks like you can apply a frame to your Item view and that prevents it from jumping. Example below:

struct ContentView: View {
var body: some View {
    ScrollView {
        LazyVStack {
            ForEach(1...40, id: \.self) { value in
                Item(value: value)
                    .id(value)
                    .padding(.bottom, -100)
            }
        }
    }
    .frame(width: 300, height: 300)
  }
}

struct Item: View {
    let value: Int
    let lorem = "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."
    
    var body: some View {
        Group {
            if value < 20 {
                Text("\(value) A long text: \(lorem)")
            }
            else {
                Text("\(value) A short text")
            }
        }
        .frame(width: 300, height: 300)
    }
}

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

CodePudding user response:

struct ContentView: View {
    var body: some View {
        ScrollView {
            ScrollView {
                LazyVStack {
                    ForEach(1...40, id: \.self) { value in
                        Item(value: value)
                            .id(value)
                            .padding()
                    }
                }
                .padding()
            }
            .frame(width: 300, height: 300)
        }
    }
}
  • Related