scrollPosition(id:) emits a stale target ID and hangs a paged ScrollView

FB24767077

After programmatically setting the ID to B, manually paging back to A causes the binding to update to A and then unexpectedly back to B. The view hangs between pages while SwiftUI attempts to animate toward the stale B target.

The issue occurs only when an app built with Xcode 27 runs on iOS 27. It does not occur in:

Xcode 27 build running on iOS 26

Xcode 26 build running on iOS 27

Xcode 26 build running on iOS 26

Steps to reproduce:

  1. Launch the minimal reproduction project
  2. Tap “Set B” to assign B directly to the scrollPosition(id:) binding
  3. Swipe right to return to page A

Expected result:

After paging from B back to A, the scroll position remains A once the pager settles.

Actual result:

The binding emits A followed by B, even though page A is the visible, settled page. The resulting stale B value causes the animation to hang between pages.

Code:

struct ContentView: View {
    @State private var selectedPage: Page? = .a

    var body: some View {
        VStack {
            HStack {
                Button("Set B") {
                    selectedPage = .b
                }

                Text("Selected: \(selectedPage?.rawValue ?? "nil")")
            }

            PagerView(selectedPage: $selectedPage)
        }
        .onChange(of: selectedPage, initial: false) { _, newValue in
            print("selectedPage: \(newValue?.rawValue ?? "nil")")
        }
    }
}

struct PagerView: View {
    @Binding var selectedPage: Page?

    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(spacing: .zero) {
                ForEach(Page.allCases) { page in
                    Text(page.rawValue)
                        .font(.largeTitle)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                        .background(page.color)
                        .containerRelativeFrame(.horizontal)
                }
            }
            .scrollTargetLayout()
        }
        .frame(height: 300)
        .scrollIndicators(.hidden)
        .scrollPosition(id: $selectedPage)
        .scrollTargetBehavior(.paging)
        .animation(.default, value: selectedPage)
    }
}

enum Page: String, CaseIterable, Identifiable {
    case a = "A"
    case b = "B"
    case c = "C"

    var id: Self { self }

    var color: Color {
        switch self {
        case .a: .red.opacity(0.2)
        case .b: .green.opacity(0.2)
        case .c: .blue.opacity(0.2)
        }
    }
}
scrollPosition(id:) emits a stale target ID and hangs a paged ScrollView
 
 
Q