Reputation: 783
Trying to implement a TabView
with PageTabView
style in SwiftUI
, where navigation is only done programmatically, and all swipe gestures are disabled.
This solution only partially works - if you tap the screen as the selection is changing, it still interferes with the transition and causes weird effects. Also, if you scroll with two fingers the gesture still registers. I need a solution that fully disables the swipe gesture.
Code:
struct PageViewTest: View {
@State var selection: Int = 1
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
TabView(selection: $selection) {
Color.red
.tag(1)
.gesture(DragGesture())
Color.blue
.tag(2)
.gesture(DragGesture())
Color.yellow
.tag(3)
.gesture(DragGesture())
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.animation(.linear, value: selection)
VStack {
Spacer()
Button(action: {
selection = selection == 3 ? 1 : selection + 1
}) {
Text("next")
.foregroundColor(.white)
.font(.title)
}
}
}
}
}
setting .disabled(true)
to the TabView
solves it, but then all of the subviews are no longer interactive.
Upvotes: 3
Views: 5106
Reputation: 1
There is a simpler way
TabView(selection: $selection) {
View1()
.tag(0)
.contentShape(Rectangle()).gesture(DragGesture()) // <- use This
View2()
.tag(1)
.contentShape(Rectangle()).gesture(DragGesture()) // <- use This
View3()
.tag(2)
.contentShape(Rectangle()).gesture(DragGesture()) // <- use This
}
.tabViewStyle(.page(indexDisplayMode: .never))
Upvotes: 0
Reputation: 592
Any update on disabling the 2 finger swipe on TabView
in SwiftUI when using page style?
I have been facing the same issue and not able to disable it for TabView in SwiftUI. But I changed my implementation using ScrollView to implement the TabView effect in one of my App.
struct TabContentView: View {
private var views: [AnyView] = []
@State private var currentIndex: Int = 0
init() {
self.views = [
// The below are your components
AnyView(Page1()),
AnyView(Page2()),
AnyView(Page3())
]
}
var body: some View {
GeometryReader { proxy in
ScrollViewReader { value in
TabNavigationBar(
currentIndex: $currentIndex,
pageCount: views.count
)
.padding()
.background(.green)
ScrollView(.horizontal) {
HStack {
ForEach(0..<views.count, id: \.description) { index in
self.views[index]
.id(index) // We need to set the id to identify the view when trying to scroll programatically
.frame(width: proxy.size.width)
}
}
}
.scrollDisabled(true) // To disable scroll
.transition(.slide)
.onChange(of: self.currentIndex) { oldValue, newValue in
withAnimation {
value.scrollTo(newValue) // To programatically scroll to particular id
}
}
}
}
}
}
struct TabNavigationBar: View {
@Binding var currentIndex: Int
let pageCount: Int
var body: some View {
HStack {
if currentIndex != 0 {
Button {
if currentIndex > 0 {
currentIndex -= 1
}
} label: {
Text("back")
}
}
Spacer()
if currentIndex != pageCount - 1 {
Button {
if currentIndex < pageCount {
currentIndex += 1
}
} label: {
Text("next")
}
}
}
}
}
Upvotes: 0
Reputation: 573
This is to help others, after some hours of research I believe the following is a solid simple solution using SwiftUI-Introspec Library on github. If you simply remove the gestures on the first view. It makes your problems go away. :]. You can also build your custom PagerView to avoid this problem altogether using Majid's solution as mentioned here previously.
TabView(selection: $selection) {
Color.red
.tag(0)
.introspectScrollView { scrollView in
scrollView.gestureRecognizers?.removeAll()
}
Color.blue
.tag(1)
Color.yellow
.tag(2)
}
Upvotes: 0
Reputation: 36
This answer allows the creation of an overlay with a custom gesture recognizer in SwiftUI. Then all we need is to create a delegate that won't allow the gesture to start.
So the code would be:
import SwiftUI
import UIKit
struct ContentView: View {
@State var selection: Int = 1
let numTabs = 3
let minDragTranslationForSwipe: CGFloat = 5000
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
TabView(selection: $selection) {
Color.red
.tag(1)
Color.blue
.tag(2)
Color.yellow
.tag(3)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.animation(.linear, value: selection)
.overlay(TouchesHandler())
VStack {
Spacer()
Button(action: {
selection = selection == 3 ? 1 : selection + 1
}) {
Text("next")
.foregroundColor(.white)
.font(.title)
}
}
}
}
}
//just a dummy
class MySwipeGesture: UISwipeGestureRecognizer {
@objc func noop() {}
init(target: Any?) {
super.init(target: target, action: #selector(noop))
}
}
//this delegate effectively disables the gesure
class MySwipeGestureDelegate: NSObject, UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
false
}
}
//and the overlay inspired by the answer from the link above
struct TouchesHandler: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<TouchesHandler>) -> UIView {
let view = UIView(frame: .zero)
view.isUserInteractionEnabled = true
view.addGestureRecognizer(context.coordinator.makeGesture())
return view;
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<TouchesHandler>) {
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator {
var delegate: UIGestureRecognizerDelegate = MySwipeGestureDelegate()
func makeGesture() -> MySwipeGesture {
delegate = MySwipeGestureDelegate()
let gr = MySwipeGesture(target: self)
gr.delegate = delegate
return gr
}
}
typealias UIViewType = UIView
}
Upvotes: 0