I'm creating a horizontal menu and I'm using horizontal ScrollView and an HStack to view its items. Everything seems to work but I have a problem with ScrollViewReader.. As you should from the code when currentIndex changes I pass its value to the proxy.scrollTo() method but I don't get any results, the ScrollView does not follow the modification of currentIndex remaining still in its original position. Can you tell me where I'm going wrong? thank you all
struct ScrollableTabMenu<T:CaseIterable & Hashable & Identifiable>: View {
var items: [T]
var title: KeyPath<T, String>
@Binding var currentIndex: Int
@Namespace private var animation
var body: some View {
ScrollView(.horizontal) {
ScrollViewReader { proxy in
HStack {
ForEach(items) { screen in
let index = items.firstIndex(of: screen)!
Button {
currentIndex = index
} label: {
Text(screen[keyPath: title])
.padding()
.overlay(alignment: .bottom){
if currentIndex == index {
RoundedRectangle(cornerRadius: 6)
.frame(height: 1)
.matchedGeometryEffect(id: "AninamtionTAB", in: animation)
}
}
}
.buttonStyle(.plain)
}
}
.onChange(of: currentIndex, { _, newValue in
withAnimation {
proxy.scrollTo(newValue, anchor: .leading)
print(newValue)
}
})
}
}
.scrollTargetBehavior(.paging)
.scrollIndicators(.hidden)
.contentMargins(.horizontal, 16)
}
}
scrollTotakes the id of the view that you are scrolling to. All your buttons have the id that is automatically given byForEach, which is the corresponding value ofTinitems, not anIntindex number.If you want to use the index as the id, you should add
.id(index)to the buttons.However, indices are rather unstable - they change whenever you add/remove a tab. I would suggest using a
Binding<T>to represent the current tab.Note that now you don't even need to add your own
.id(screen)- the id thatForEachautomatically assigns to the buttons is exactly the id that we want them to have.Using the
.pagingscroll behaviour is rather weird in this context. The pages doesn't necessarily align with the tab buttons, so when you tap on a tab, the button gets scrolled to the left, and might get "cut off", because that happens to be where a new "page" begins.I prefer using
.scrollTargetBehavior(.viewAligned), and setting theHStackas the.scrollTargetLayout().I would also not use a
KeyPath<T, String>. This doesn't allow for localisation. I would take in a(T) -> Textor(T) -> LabelwhereLabelis a generic type parameter constrained toView.