1

I'm trying to disable the possibility to swipe a TabView in swiftui while a variable (Bool) is set to true but I must miss something very simple. I found an answer here as well as many other posts saying the same but when I run a test it doesn't prevent the swipe for me.

I have a simple test code:

struct TabViewTest: View {
@State var editing: Bool = false
var numbers:[Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

var body: some View {
    TabView {
        ForEach(numbers, id: \.self) { index in
            Button ("Hello, World \(index)!") {
                editing = !editing
                print("Btn \(index) changed editing to \(editing)")
            }
            .gesture(editing ? DragGesture() : nil)
        }
    }
    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
    .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .never))
}

}

Yet when running this I still can swipe no matter if the "editing" variable is true or false.

Here is the log from pressing the button on each Tab:

Btn 1 changed editing to true

Btn 2 changed editing to false

Btn 3 changed editing to true

Btn 4 changed editing to false

Btn 5 changed editing to true

Btn 6 changed editing to false

What am I missing? everything I find about this offer the same solution yet I'm not able to implement it ... I must miss something basic ...

I have this on the simulator with IOS 16 if this would make a difference.

Edit: it look like this is an issue others face but have not found a solution to yet, I have found a similar question here.

Slamit
  • 465
  • 1
  • 6
  • 21

2 Answers2

1

Your idea of using a DragGesture to prevent TabView from seeing the drag is good, but you need the DragGesture to apply to the whole screen, not just to the button.

struct TabLocker: View {
    let pages = Array(1...10)
    @State var locked = false

    var body: some View {
        TabView {
            ForEach(pages, id: \.self) { page in
                ZStack {
                    // Any Color expands to take as much room as possible,
                    // so it will fill the screen.
                    // Color.clear is invisible so it won't affect the page appearance.
                    // But because it's invisible, SwiftUI doesn't allow it
                    // to receive touches by default.
                    // The .contentShape(Rectangle()) allows it to receive touches.
                    Color.clear
                        .contentShape(Rectangle())
                        .gesture(locked ? DragGesture() : nil)
                    VStack {
                        Text("Page \(page)")
                        Toggle("Locked", isOn: $locked)
                            .fixedSize()
                    }
                }
            }
        }
        .tabViewStyle(.page(indexDisplayMode: .never))
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • That's an interesting approach. I tried it but the issue remain. I just found a solution using "simultaneousGesture" though. the issue is that Button has a onTapGesture and this seems to cause problems except if "simultaneousGesture" is used. I found that here: https://stackoverflow.com/questions/65524458/swiftui-tabview-pagetabviewstyle-prevent-changing-tab – Slamit Feb 15 '23 at 16:31
0

I actually found an answer in the comments of this question.

The issue is: any view that has an "onTapGesture" will ignore ".gesture(...)".

The solution is to use ".simulataneousGesture(...)" instead to ensure the gesture is capture and handled by both view/modifier.

It worked perfectly in my case after changing it. The only exception is for 2 finger drag gesture.

In my case the following code worked:

 ForEach(numbers, id: \.self) { index in
        Button ("Hello, World \(index)!") {
            editing = !editing
            print("Btn \(index) changed editing to \(editing)")
        }
        .simulataneousGesture(editing ? DragGesture() : nil)
    }
Slamit
  • 465
  • 1
  • 6
  • 21