ios – SwiftUI: Maintain scroll position when a View within a ScrollView changes size


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

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img