Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

ScrollPosition.scrollTo(id:, anchor:) not behaving as expected

While trying the new ScrollPosition API I noticed that scrollTo(id: anchor:) behaves different than ScrollViewProxy.scrollTo(_: anchor:).

Consider the following example:

struct ContentView: View {

    @State private var position = ScrollPosition(edge: .top)
        
    var body: some View {
        NavigationStack {
            ScrollViewReader { proxy in
                ScrollView {
                    VStack(spacing: 8) {
                        ForEach(1..<100) { index in
                            Text(verbatim: index.formatted())
                                .frame(maxWidth: .infinity)
                                .background(.gray)
                                .id(index)
                        }
                    }
                }
                .scrollPosition($position)
                .toolbar {
                    ToolbarItemGroup(placement: .bottomBar) {
                        Spacer()
                        Button("50 (T)") {
                            withAnimation {
                                position.scrollTo(id: 50, anchor: .top)
//                                proxy.scrollTo(50, anchor: .top)
                            }
                        }
                        Button("50 (B)") {
                            withAnimation {
                                position.scrollTo(id: 50, anchor: .bottom)
//                                proxy.scrollTo(50, anchor: .bottom)
                            }
                        }
                        Spacer()
                    }
                }
            }
        }
    }
    
}

The position methods don't align top and bottom edges, but the proxy ones do.

Is this expected or is it a bug?

They share semantics and I wouldn't say the API's are the same. The API docs for ScrollPosition covers the use of the anchor.

The ScrollPosition anchor Influences which view the system chooses as the view whose identity value will update the providing binding as the scroll view scrolls and Controls the alignment of the view when scrolling to a view when writing a new binding value.

In your example:

 position.scrollTo(id: 50, anchor: .bottom)

This will prefer to have the bottom-most view chosen and prefer to scroll to views aligned to the bottom.

I was able to achieve the same behavior by changing the anchor of scrollPosition(_:anchor:) to match scrollTo(id:anchor:) before calling:

struct ContentView: View {
 
    @State private var position = ScrollPosition(edge: .top)
    
    @State private var anchor: UnitPoint?
        
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(spacing: 8) {
                    ForEach(1..<100) { index in
                        Text(verbatim: index.formatted())
                            .frame(maxWidth: .infinity)
                            .background(.gray)
                            .id(index)
                    }
                }
            }
            .scrollPosition($position, anchor: anchor)
            .toolbar {
                ToolbarItemGroup(placement: .bottomBar) {
                    Spacer()
                    Button("50 (T)") {
                        anchor = .top
                        withAnimation {
                            position.scrollTo(id: 50, anchor: .top)
                        }
                    }
                    Button("50 (B)") {
                        anchor = .bottom
                        withAnimation {
                            position.scrollTo(id: 50, anchor: .bottom)
                        }
                    }
                    Spacer()
                }
            }
        }
    }
    
}
ScrollPosition.scrollTo(id:, anchor:) not behaving as expected
 
 
Q