15

How can I disable the swipe-back gesture in SwiftUI? The child view should only be dismissed with a back-button.

simibac
  • 7,672
  • 3
  • 36
  • 48

7 Answers7

17

By hiding the back-button in the navigation bar, the swipe-back gesture is disabled. You can set a custom back-button with .navigationBarItems()

struct ContentView: View {
    var body: some View {
        NavigationView{
            List{
                NavigationLink(destination: Text("You can swipe back")){
                    Text("Child 1")
                }
                NavigationLink(destination: ChildView()){
                    Text("Child 2")
                }
            }
        }
    }
}

struct ChildView: View{
    @Environment(\.presentationMode) var presentationMode

    var body:some View{
        Text("You cannot swipe back")
            .navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: Button("Back"){self.presentationMode.wrappedValue.dismiss()})
    }
}

simibac
  • 7,672
  • 3
  • 36
  • 48
  • 2
    This works, but looses you at least the localization (i.e. internationalization) of the back button which you might care about or not. – SirVer Jan 26 '20 at 17:21
  • 4
    is there a way to hide the back button without losing swipe-back? – bze12 Dec 30 '20 at 03:26
  • @SirVer shouldn't it be fine to use a localized string in your "Back" button? If you care about internationalization, that shouldn't be hard. – Rillieux Feb 10 '22 at 20:59
  • You also lose the back button smartness that it only shows the icon if the title does not fit. – bio Jul 03 '22 at 12:43
6

Only complete removal of the gesture recognizer worked for me.
I wrapped it up into a single modifier (to be added to the detail view).

struct ContentView: View {
    
    var body: some View {
        VStack {
            ...
        )
        .disableSwipeBack()
    }
}

DisableSwipeBack.swift

import Foundation
import SwiftUI

extension View {
    func disableSwipeBack() -> some View {
        self.background(
            DisableSwipeBackView()
        )
    }
}

struct DisableSwipeBackView: UIViewControllerRepresentable {
    
    typealias UIViewControllerType = DisableSwipeBackViewController
    
    
    func makeUIViewController(context: Context) -> UIViewControllerType {
        UIViewControllerType()
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
    }
}

class DisableSwipeBackViewController: UIViewController {
    
    override func didMove(toParent parent: UIViewController?) {
        super.didMove(toParent: parent)
        if let parent = parent?.parent,
           let navigationController = parent.navigationController,
           let interactivePopGestureRecognizer = navigationController.interactivePopGestureRecognizer {
            navigationController.view.removeGestureRecognizer(interactivePopGestureRecognizer)
        }
    }
}

You can resolve the navigation controller without third party by using a UIViewControllerRepresentable in the SwiftUI hierarchy, then access the parent of its parent.

Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
6

Adding this extension worked for me (disables swipe back everywhere, and another way of disabling the gesture recognizer):

extension UINavigationController: UIGestureRecognizerDelegate {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }
    
    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 
        return false
    }
}
Tigeryy
  • 61
  • 1
  • 1
4

I use Introspect library then I just do:

import SwiftUI
import Introspect

struct ContentView: View {
   var body: some View {
      Text("A view that cannot be swiped back")
           .introspectNavigationController { navigationController in
              navigationController.interactivePopGestureRecognizer?.isEnabled = false
      }
   }
}
Hikeland
  • 357
  • 4
  • 7
3

Setting navigationBarBackButtonHidden to true will lose the beautiful animation when you have set the navigationTitle.

So I tried another answer

navigationController.interactivePopGestureRecognizer?.isEnabled = false

But It's not working for me.

After trying the following code works fine

NavigationLink(destination: CustomView()).introspectNavigationController {navController in
            navController.view.gestureRecognizers = []
        }

preview

李青昊
  • 31
  • 3
2

This answer shows how to configure your navigation controller in SwiftUI (In short, use UIViewControllerRepresentable to gain access to the UINavigationController). And this answer shows how to disable the swipe gesture. Combining them, we can do something like:

Text("Hello")
  .background(NavigationConfigurator { nc in
     nc.interactivePopGestureRecognizer?.isEnabled = false
  })

This way you can continue to use the built in back button functionality.

arsenius
  • 12,090
  • 7
  • 58
  • 76
1

The following more replicates the existing iOS chevron image. For the accepted answer. That is replace the "back" with image chevron.

 .navigationBarItems(leading: Button("Back"){self.presentationMode.wrappedValue.dismiss()})

With

Button(action: {self.presentationMode.wrappedValue.dismiss()}){Image(systemName: "chevron.left").foregroundColor(Color.blue).font(Font.system(size:23, design: .serif)).padding(.leading,-6)}