I am implementing an onboarding sequence in SwiftUI choreographed through a TabView, and want to only allow progression through the onboarding through buttons, so I need to disable any premature user gesturing through the entire thing. But, every method I find online to solve this problem also disables any scrollable components within the pages of the tabview, such as vertical scrollviews and other tabviews within the onboarding.
I am looking for a method to enable scrolling for components within the TabView while keeping the overarching Tabview disabled.
Here is the current implementation:
struct OnboardingView: View {
@ObservedObject var vm = OnboardingVM()
var body: some View {
VStack {
// ProgressIndicator(vm: vm)
TabView(selection: $vm.currentScreenIndex) {
ForEach(0..<vm.screenOrder.count, id:\.self) { index in
ScreenView(vm: vm, screenIndex: index)
.tag(index)
}
}.padding()
.tabViewStyle(.page(indexDisplayMode: .never))
.highPriorityGesture(DragGesture())
}.environmentObject(vm)
.background(Color.black)
}
}
I expected the highPriorityGesture modifier to only alter the tabview indexing, but it also affects the sub-components.
Instead of using TabView
, I suggest using a ScrollView
instead. Then, disabling scrolling is as simple as .scrollDisabled(true)
. Use containerRelativeFrame
to expand each onboarding screen to occupy the entirety of the scroll view container. Use .scrollPosition(id:)
to control the scroll position programmatically.
Here is an example:
struct ContentView: View {
@State private var index = 0
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 0) {
// first page
List {
ForEach(0..<10) { _ in
Text("Some Content")
}
}
.containerRelativeFrame([.horizontal, .vertical])
.id(0)
// second page
Text("Page 2")
.containerRelativeFrame([.horizontal, .vertical])
.background(.yellow)
.id(1)
// third page
VStack {
Text("Page 3")
ScrollView {
ForEach(0..<10) { _ in
Text("Some Content")
}
}
.frame(width: 160, height: 160)
}
.containerRelativeFrame([.horizontal, .vertical])
.background(.green)
.id(2)
}
}
.scrollPosition(id: Binding($index))
.scrollDisabled(true)
.containerRelativeFrame(.vertical)
.animation(.default, value: index)
// as an example, I have added the next and previous buttons as an overlay.
// these can of course be in each of the onboarding screens instead.
.overlay {
HStack {
Button("Previous") {
index -= 1
}
.disabled(index == 0)
Spacer()
Button("Next") {
index += 1
}
.disabled(index == 2)
}
.buttonStyle(.borderedProminent)
}
}
}