5

First of all, I have noticed that there is a similar question. However, I would like to ask about the solution in Swift. Here is my code:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Button", style: .Plain, target: self, action: nil)
}

I want to replace the back button but keeping the swipe for user to navigate back. However, this disables the swipe. I cannot get the accepted answer and the suggested answer in the link above to work. Here is what I tried to translated into Swift.

let appearanceNavigationBar = UINavigationBar.appearance()
appearanceNavigationBar.backIndicatorImage = UIImage(named: "back")
appearanceNavigationBar.backIndicatorTransitionMaskImage = UIImage(named: "back")
appearanceNavigationBar.tintColor = UIColor.whiteColor()

I am using Xcode 8.0 beta, Swift 2.3 and testing in iOS 10.0. Any help would be greatly appreciated.

Andriy Gordiychuk
  • 6,163
  • 1
  • 24
  • 59
Joshua
  • 5,901
  • 2
  • 32
  • 52
  • Maybe you can take a look at this property: `interactivePopGestureRecognizer ` – Siam Jul 04 '16 at 08:00
  • @Siam Setting `interactivePopGestureRecognizer.enabled = true` do nothing. Reassigning `interactivePopGestureRecognizer.delegate` lead to a lot of issues. (From the suggested answer) – Joshua Jul 04 '16 at 08:03

4 Answers4

16

I used this and it worked:

self.navigationController.interactivePopGestureRecognizer.delegate = nil;
Proton
  • 1,335
  • 1
  • 10
  • 16
  • This works. Would you mind to explain the reason(s) behind? – Joshua Jul 04 '16 at 08:47
  • 1
    I'm still don't know why. I've just referred this http://luugiathuy.com/2013/11/ios7-interactivepopgesturerecognizer-for-uinavigationcontroller-with-hidden-navigation-bar/ – Proton Jul 04 '16 at 08:54
1

The accepted answer is not complete. If you have a storyboard segue configured and you attempt a "swipe back" gesture on the first view controller you may be unable to trigger the segue.

The best solution which takes all edge cases into the account is:

class QFNavigationController:UINavigationController, UIGestureRecognizerDelegate, UINavigationControllerDelegate{
    override func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
        delegate = self
    }

    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        super.pushViewController(viewController, animated: animated)
        interactivePopGestureRecognizer?.isEnabled = false
    }

    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        interactivePopGestureRecognizer?.isEnabled = true
    }

    // IMPORTANT: without this if you attempt swipe on
    // first view controller you may be unable to push the next one
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return viewControllers.count > 1
    }

}

Now just use QFNavigationController whenever you need it.

Andriy Gordiychuk
  • 6,163
  • 1
  • 24
  • 59
0

Setting delegate to nil as described in this answer causes some nasty bugs in iOS 11, for example this one

But the idea of stubbing the delegate is good in general. For example, you can write the following code

final class NavigationController: UINavigationController {
    private var customDelegate: InteractivePopGestureRecognizerDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()

        customDelegate = InteractivePopGestureRecognizerDelegate(
            originalDelegate: interactivePopGestureRecognizer?.delegate,
            navigationController: self
        ) 
        interactivePopGestureRecognizer?.delegate = customDelegate
    }
}

Then you can use your custom delegate to fix the root cause of the problem

final class InteractivePopGestureRecognizerDelegate:
    NSObject,
    UIGestureRecognizerDelegate
{
    private weak var originalDelegate: UIGestureRecognizerDelegate?
    private weak var navigationController: UINavigationController?

    init(
        originalDelegate: UIGestureRecognizerDelegate?,
        navigationController: UINavigationController)
    {
        self.originalDelegate = originalDelegate
        self.navigationController = navigationController
    }

    func gestureRecognizer(
        _ gestureRecognizer: UIGestureRecognizer,
        shouldReceive touch: UITouch)
        -> Bool
    {
        if let originalDelegate = originalDelegate,
            let result = originalDelegate.gestureRecognizer?(
                gestureRecognizer,
                shouldReceive: touch)
        {
            if !result {
                // Your interactive pop gesture got cancelled here

                // Perform sanity check of pop possibility
                if (navigationController?.viewControllers.count ?? 0) < 2 {
                    return result
                }

                if navigationController?.navigationBar.isHidden == true {
                    // Return true here if you want to swipe even without navigation bar
                    return result
                }

                // Enable pop gesture
                return true
            }
        } else {
            return true
        }
    }

    ...
    // Proxy all other `UIGestureRecognizerDelegate` methods to the originalDelegate here
    ...
}

Of course this is a bit tricky and may not work in the future (like every other hack)

Tim
  • 1,877
  • 19
  • 27
0

Ok, so you probably don't need a solution to this problem anymore as 3 years have passed:) But maybe this will help someone else to fix this problem. It's actually pretty simple: you just need to configure the back button, which will be presented on a second view controller from first view controller. Here's an example:

class FirstViewController: UIViewController {
  override func viewDidLoad() {
    let backButton = UIBarButtonItem(image: UIImage(systemName: "chevron.left.circle.fill"), style: .done, target: nil, action: nil)
    navigationItem.backBarButtonItem = backButton
  }

  func presentSecondViewController() {
    navigationController?.pushViewController(SecondViewController(), animated: true)
  }
}

Configuring FirstViewController this way will allow you to display custom back button on the SecondViewController and also keep swipe-back gesture.

Maxim Skryabin
  • 385
  • 2
  • 11