SwiftUI ScrollView with .viewAligned scrollTargetBehavior doesn't scroll to correct index when using buttons

5 days ago 2
ARTICLE AD BOX

This problem seems to be an issue with the ids of the scrolled items. It probably also doesn't help, that you are using separate state variables for selectedButtonsIndex and scrollPosition and using onChange handlers to ripple changes from one to the other.

I would suggest the following changes:

Use selectedButtonsIndex as the parameter to .scrollPosition and remove the .onChange modifiers. The variable scrollPosition is no longer needed.

For the cells collection, use the offset of the array enumeration as the id for ForEach, instead of element. There is then no need to apply an .id to the items.

// @State private var scrollPosition: Int? = 0 // Cells Collection ScrollView(.horizontal) { HStack(spacing: 10) { ForEach(Array(items.enumerated()), id: \.offset) { index, item in CardView(number: item, isSelected: selectedButtonsIndex == index) .containerRelativeFrame(.horizontal) { width, axis in return width * 0.98 } .onTapGesture { withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) { selectedButtonsIndex = index } } } } .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned) .safeAreaPadding(.horizontal, 20) .scrollPosition(id: $selectedButtonsIndex) // .onChange deleted

Btw, you might notice that when you tap a button to scroll to an item, it is not quite centered. The reason is because the width of an item is being set to width * 0.98 in the .containerRelativeFrame closure. To fix, you could either change the closure to return the full width, or you need to add horizontal .contentMargins to the ScrollView. The horizontal content margins need to be 0.01 of the ScrollView width, this being the remainder after deducting the width of an item and dividing by 2. One way to measure the width of the ScrollView would be to wrap it with a GeometryReader.

Also, when scrolling between items, you will notice that the position of the neighbouring item sometimes "shuffles" a bit. This is especially noticeable when scrolling back to the first item. This is happening because the container is lazy. The problem can sometimes be solved by applying .geometryGroup() (ref. SwiftUI content jumping issue with LazyHStack embedded in a horizontal ScrollView in iOS 18), but in fact I found it didn't help much here. However, changing LazyHStack to HStack does fix it.

Read Entire Article