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)
}
}