bze12
bze12

Reputation: 783

Disable Swipe Gesture in SwifUI TabView

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

Answers (4)

Junior Obici
Junior Obici

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

Bibin Jaimon
Bibin Jaimon

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.

TabView using ScrollView

Implementation of TabView using ScrollView

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
                    }
                    
                }
            }
        }
    }
    
}

Implementation of Header View where back and next button has implemented

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

Wael
Wael

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

Dan O
Dan O

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

Related Questions