I'm working on a horizontally scrollable header view. I'd like to implement the following behavior: when users tap on the left/rightmost header items, the header would scroll so they become fully visible. I am using a custom PreferenceKey
to report the selected index:
struct SelectedTabPreference: PreferenceKey {
static var defaultValue: Int = 0
static func reduce(value: inout Int, nextValue: () -> Int) {}
}
Then in my view, I wrap my content inside a ScrollViewReader
to scroll to the selected header:
struct ContentView: View {
let tabs: [DrawerTab]
@State private var selectedIndex: Int = 0
var body: some View {
ScrollViewReader { scrollProxy in
ScrollView(.horizontal, showsIndicators: false) {
TopTabView(options: tabs, selectedIndex: $selectedIndex)
.padding(32)
.background(Color.clear.preference(key: SelectedTabPreference.self, value: selectedIndex))
}
.onPreferenceChange(SelectedTabPreference.self) { value in
let firstTabSelected = value == 0
let lastTabSelected = value == tabs.count - 1
guard firstTabSelected || lastTabSelected else { return }
withAnimation {
let anchor: UnitPoint = firstTabSelected ? UnitPoint(x: 0.1, y: 0) : UnitPoint(x: 0.9, y: 0)
scrollProxy.scrollTo(tabs[value], anchor: anchor)
}
}
}
}
}
This works well when I run the app, but the Xcode Preview only shows a white screen. In fact, if I include this view as a subview in other views, their previews also break. The Preview only returns to life if I remove the programmatic scrolling:
scrollProxy.scrollTo(tabs[value], anchor: anchor)
I'm pretty sure this has to do with the ScrollViewProxy
as my other Preference-based layouts work with Previews, but I don't know how to resolve the issue. Xcode Previews are an integral part of my workflow, and I'd like to avoid losing them if there's a chance.