How can I use specify the anchor used to display an item that a user scrolls to ?

I have a scrollview displaying a sequence of circles, which a user should be able to scroll through to select an item. When the user stops scrolling and the animation comes to rest the circle selected should display screen-centered.

I had hoped to achieve this using .scrollPosition(id: selectedItem, anchor: .center) but it appears that the anchor argument is ignored when scrolled manually. (BTW - I searched but didn't locate this aspect in the Apple documentation so I'm not confident that this observation is really correct).

https://youtu.be/TpXDTuL5yPQ

The video shows the user-scrolling behaviour, and also the snap-to-anchor that I would like to achieve, but I would like this WITHOUT forcing a button press.

I could juggle the container size and size of the circles so that they naturally fit centered into the screen, but I would prefer a more elegant solution.

How can I force the scrolling to come to rest such that the circle glides to rest in the center of the screen/container?


struct ItemChooser: View {
    @State  var selectedItem: Int?
    
    var body: some View {
        
        VStack {
            Text("You have picked: \(selectedItem ?? 0)")
            ScrollHorizontalItemChooser(selectedItem: $selectedItem)
        }
    }
}

#Preview {
    ItemChooser(selectedItem: 1)
}

struct ScrollHorizontalItemChooser: View {
    @Binding  var selectedItem: Int?
    @State var scrollAlignment: UnitPoint? = .center
    let ballSize: CGFloat = 150
    
    let items = Array(1...6)
    
    @State var scrollPosition: ScrollPosition = ScrollPosition()
    
    var body: some View {
        VStack {
            
            squareUpButton
   
            ScrollView(.horizontal) {
                
                HStack(spacing: 10) {
                    showBalls
                }
                .scrollTargetLayout()
            }

            .scrollPosition(id: $selectedItem,  anchor: scrollAlignment )

            .overlay{ crosshairs }        }
    }
    
    var crosshairs: some View {
        Image(systemName: "scope").scaleEffect(3.0).opacity(0.3)
    }
    
    @ViewBuilder
    var showBalls: some View {
        let screenWidth: CGFloat = UIScreen.main.bounds.width
        
        var emptySpace: CGFloat {screenWidth / 2 - ballSize / 2 - 10}
        
        Spacer(minLength: emptySpace)
        
        ForEach(items, id: \.self) { item in
            
            poolBall( item)
                .id(item)
        }
        Spacer(minLength: emptySpace)
    }
    
    @ViewBuilder
    private func poolBall(_ item: Int) -> some View {
        
        Text("Item \(item)")
            .background {
                Circle()
                    .foregroundColor(Color.green)
                    .frame(width: ballSize, height: ballSize)
            }
            .frame(width: ballSize, height: ballSize)
    }
    
    
    @ViewBuilder
    var squareUpButton: some View {
        var tempSelected: Int? = nil
        Button("Square up with Anchor") {
          
            tempSelected = selectedItem
            selectedItem = 0
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
               
                selectedItem = tempSelected ?? 0
            }
        }
    }
}


@Alanrick You would need to write a custom implementation for ScrollTargetBehavior that meets your specific use case. This would allow you create a custom scroll behavior.

How can I use specify the anchor used to display an item that a user scrolls to ?
 
 
Q