13

I have a TabView thats using the swiftUI 2.0 PageTabViewStyle. Is there any way to disable the swipe to change pages?

I have a search bar in my first tab view, but if a user is typing, I don't want to give the ability to change they are on, I basically want it to be locked on to that screen until said function is done.

Here's a gif showing the difference, I'm looking to disable tab changing when it's full screen in the gif. https://imgur.com/GrqcGCI

daee kang
  • 175
  • 2
  • 7

3 Answers3

8

Try something like the following (tested with some stub code). The idea is to block tab view drag gesture when some condition (in you case start editing) happens

@State var isSearching = false

// ... other code

TabView {
    // ... your code here

    Your_View()
       .gesture(isSearching ? DragGesture() : nil)  // blocks TabView gesture !!
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
Asperi
  • 173,274
  • 14
  • 284
  • 455
  • This partially works. It only stops the user from trying to drag starting from visible elements on the display (they can still drag from the other areas) and doesn't disable the index dots below. – Jacob Wood Aug 17 '20 at 18:47
  • The index dots below are from the `indexDisplayMode`, you can remove them by setting it to `.never` @JacobWood – mwright Sep 03 '20 at 21:21
  • .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .never)) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) is what I used to disable the dots and their background – FontFamily Dec 25 '20 at 20:18
  • @JacobWood did you get this to work reliably? the gesture override is buggy for me – bze12 Dec 31 '20 at 05:20
  • 1
    Same - the swipe gesture still appears to change the page. I actually opened a new question (https://stackoverflow.com/questions/65524458/swiftui-tabview-pagetabviewstyle-prevent-changing-tab) about it, but can close it if the answer here is updated. Thanks! – Hoopes Dec 31 '20 at 18:30
  • I didn't end up getting a flawless solution. I came close but the user could still tap the page indicators to switch between tabs. I decided to go with a different design altogether and would encourage anybody looking to use this to just create their own custom view. – Jacob Wood Jan 11 '21 at 15:35
  • @JacobWood can you share what solution you used? This solution works, except when I programmatically change the page (e.g. via a button), I can intercept the change if I tap the screen. – bze12 Mar 03 '21 at 01:39
  • I didn't continue with this design. If I were to do it again I'd create a custom view and handle the swipes myself. – Jacob Wood Mar 08 '21 at 13:52
1

I tried Asperis's solution, but I still couldn't disable the swiping, and adding disabled to true didn't work since I want the child views to be interactive. The solution that worked for me was using Majid's (https://swiftwithmajid.com/2019/12/25/building-pager-view-in-swiftui/) custom Pager View and adding a conditional like Asperi's solution.

Majid's PagerView with conditional:

import SwiftUI

struct PagerView<Content: View>: View {
    let pageCount: Int
    @Binding var canDrag: Bool
    @Binding var currentIndex: Int
    let content: Content
    
    
    init(pageCount: Int, canDrag: Binding<Bool>, currentIndex: Binding<Int>, @ViewBuilder content: () -> Content) {
        self.pageCount = pageCount
        self._canDrag = canDrag
        self._currentIndex = currentIndex
        self.content = content()
    }
    
    
    @GestureState private var translation: CGFloat = 0
    
    var body: some View {
        GeometryReader { geometry in
            HStack(spacing: 0) {
                self.content.frame(width: geometry.size.width)
            }
            .frame(width: geometry.size.width, alignment: .leading)
            .offset(x: -CGFloat(self.currentIndex) * geometry.size.width)
            .offset(x: self.translation)
            .animation(.interactiveSpring(), value: currentIndex)
            .animation(.interactiveSpring(), value: translation)
            .gesture(!canDrag ? nil : // <- here
                
                DragGesture()
                    .updating(self.$translation) { value, state, _ in
                        
                        state = value.translation.width
                    }
                    .onEnded { value in
                        let offset = value.translation.width / geometry.size.width
                        let newIndex = (CGFloat(self.currentIndex) - offset).rounded()
                        self.currentIndex = min(max(Int(newIndex), 0), self.pageCount - 1)
                    }
            )
        }
    }
    
}

ContentView:

import SwiftUI

struct ContentView: View {
    
    
    @State private var currentPage = 0
    @State var canDrag: Bool = true
    
    
    var body: some View {
        
        PagerView(pageCount: 3, canDrag: $canDrag, currentIndex: $currentPage) {
            VStack {
                Color.blue
                
                
                Button {
                    canDrag.toggle()
                } label: {
                    Text("Toogle drag")
                }

            }
            
            VStack {
                Color.red
                
                
                Button {
                    canDrag.toggle()
                } label: {
                    Text("Toogle drag")
                }

            }
            
            VStack {
                Color.green
                
                
                Button {
                    canDrag.toggle()
                } label: {
                    Text("Toogle drag")
                }

            }
            
        }
        
    }
    
}
vebbis
  • 81
  • 1
  • 5
  • Gestures doesn't work on transparent views by default. I made your code work by adding ```.contentShape(Rectangle())``` before adding the gesture. See: https://stackoverflow.com/questions/59008409/swiftui-vstack-hstack-zstack-drag-gesture-not-working – hardyfelix Feb 15 '22 at 12:00
  • Is it possible to add some kind of `PageTabViewStyle` so I can see dots at bottom? – Nikola C Feb 19 '22 at 14:26
0

For anyone trying to figure this out, I managed to do this by setting the TabView state to disabled.

TabView(selection: $currentIndex.animation()) {
    Items()
 }.disabled(true)
 

Edit: as mentioned in the comments this will disable everything within the TabView as well

Goopher
  • 107
  • 8