1

Goal

I have a list of items, and each can be expanded to show some additional content. However, when expanding/collapsing the item, the scroll position sometimes jumps about. Is there a way to maintain scroll position when one item changes size?

Ideally, the top edge of the view that's expanding/collapsing should remain in a fixed position on the screen.

Alternative Design?

Alternatively, is this expanding style inappropriate on iOS? If so, is there a recommended best practice for showing more content (like seeing "more comments", expanding a quote, see a longer caption, etc.)

Attempted Solutions

  • Using ScrollViewReader and scrollTo() - Unfortunately the timing of the expanded Bool toggle and scrollTo are inconsistent. This means that the view sometimes scrolls before the size has been adjusted (thus landing in the wrong spot). I tried a few permutations of grouping the two actions in the same withAnimation block, Tasks, etc. - but it always looked janky & incorrect
  • .scrollPosition() modifier - Only available in iOS 17, so realistically won't apply to most users for quite a while
  • withAnimation completion block - Also only in iOS 17

Example Code

struct MyList: View {
    var items: [String] = ["Item A", "Item B", "Item C", "Item D", "Item E", "Item F", "Item G", "Item H", "Item I", "Item J" ]
    
    var body: some View {
        ScrollView {
            LazyVStack(alignment: .center) {
                ForEach(items, id: \.self) { item in
                    ItemView(item: item)
                }
            }
            .background(.ultraThinMaterial)
        }
    }
}

struct ItemView: View {
    var item: String
    
    @State var expanded: Bool = false
    
    var body: some View {
        
        VStack {
            Text(item)
            Spacer(minLength: 50)
            Button("Expand/Collapse") {
                expanded.toggle()
            }
            if expanded {
                Text("Shown When Expanded")
                Text("\n -\n -\n -\n -\n -\n -\n -\n -\n -\n -\n -\n -")
            }
        }
        .frame(maxWidth: .infinity)
        .background()
        .padding(.bottom)
    }
}

Demonstration of above code

1 Answer 1

1

Use a VStack instead of a LazyVStack and you'll get the behavior you're looking for:

struct MyList: View {
    var items: [String] = ["Item A", "Item B", "Item C", "Item D", "Item E", "Item F", "Item G", "Item H", "Item I", "Item J" ]
    
    var body: some View {
        ScrollView {
            VStack(alignment: .center) {
                ForEach(items, id: \.self) { item in
                    ItemView(item: item)
                }
            }
            .background(.ultraThinMaterial)
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

I was facing a jumpy scroll when I used ScrollView with scrollPosition and a LazyVStack. replacing it with VStack solved the problem but I can't leave it as a VStack. Is there a way to solve this problem while still using LazyVStack?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.