9

I have a UIViewController that gets pushed onto a navigation stack. I'd like to extend the standard iOS7 interactive pan gesture to pop this view controller beyond the default UIRectEdgeLeft boundaries, so that users can initiate an interactive back action by panning from anywhere on the view.

I've tried rolling my own interactive view controller transition, but it's a lot of hassle to fully replicate the nice parallax handling of the default interactivePopGestureRecognizer. For example, the fromViewController hides the navigation bar, while the toViewController shows it—something that is not easy to handle in a custom interactive transition, but is seamless in the default action.

As a result, I want to extend the default action to a larger area of pan gesture, but the API doesn't seem to support simply replacing the gesture.

Any creative suggestions?

chancyWu
  • 14,073
  • 11
  • 62
  • 81
sirvine
  • 1,045
  • 11
  • 12
  • Try creating a pan gesture recognizer over the entire view, and set the delegate to be the navigation controller. Not sure if it would work, so posting as a comment. I do know the navigation controller can function as the delegate of the edge gesture recognizer. May work for pan too. – Léo Natan Dec 21 '13 at 02:05
  • Good edits, Chancy—thanks. Got a bit lazy. – sirvine Dec 21 '13 at 05:24
  • @LeoNatan I tried this approach. Just assigning a pan gesture's delegate to the nav controller doesn't work. (In my case, I've also got a custom navigation controller class that adopts the `UINavigationControllerDelegate` and `UIGestureRecognizerDelegate` protocols.) For good measure (i.e., desperation) I tried assigning the gesture's delegate to self.navigationController.interactivePopGestureRecognizer, but this was also a dead end. – sirvine Dec 21 '13 at 05:32
  • Did you figure this out? – Stian Høiland Feb 09 '14 at 05:00
  • No, and my solution is still glitchy on some pulls. It's becoming a common pattern in apps on the store, though, so hopefully someone will step up with an answer. – sirvine Feb 10 '14 at 06:59
  • @sirvine : I answered a similar question with a *working-but-to-be-improved* solution : http://stackoverflow.com/a/22244990/1503967 – rdurand Mar 07 '14 at 11:10
  • @StianHøiland : same comment – rdurand Mar 07 '14 at 11:11
  • 1
    [Here] [1] The question answer available in the link [1]: http://stackoverflow.com/questions/22244688/navigation-pop-view-when-swipe-right-like-instagram-iphone-app-how-i-achieve-thi – Rajesh Kumar Aug 04 '14 at 12:32
  • @sirvine this is probably the best answer: https://stackoverflow.com/questions/32914006/57487641#57487641 – Kugutsumen Aug 14 '19 at 11:19

2 Answers2

4

Check out my library SloppySwiper, which achieves this by using UIPanGestureRecognizer and by recreating the default animation. You can also see my ideas in https://github.com/fastred/SloppySwiper/issues/1.

Arek Holko
  • 8,966
  • 4
  • 28
  • 46
  • I've stumbled upon the same problem and saw your answer. I've installed the project and added it to my navigation controller as instructed. However, nothing works now. Not even the default edge gesture recognizer. Any ideas why? – Can Poyrazoğlu Oct 06 '17 at 11:01
4

It's actually quite easy to do on the UINavigationController subclass without any intervention into every UIViewController subclass pushed. Also respecting built-in swipe-from-edge state (so when it's disabled for some reason, the new gesture is disabled as well):

import UIKit

class NavigationController: UINavigationController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setupFullWidthBackGesture()
    }

    private lazy var fullWidthBackGestureRecognizer = UIPanGestureRecognizer()

    private func setupFullWidthBackGesture() {
        // The trick here is to wire up our full-width `fullWidthBackGestureRecognizer` to execute the same handler as
        // the system `interactivePopGestureRecognizer`. That's done by assigning the same "targets" (effectively
        // object and selector) of the system one to our gesture recognizer.
        guard
            let interactivePopGestureRecognizer = interactivePopGestureRecognizer,
            let targets = interactivePopGestureRecognizer.value(forKey: "targets")
        else {
            return
        }

        fullWidthBackGestureRecognizer.setValue(targets, forKey: "targets")
        fullWidthBackGestureRecognizer.delegate = self
        view.addGestureRecognizer(fullWidthBackGestureRecognizer)
    }
}

extension NavigationController: UIGestureRecognizerDelegate {
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        let isSystemSwipeToBackEnabled = interactivePopGestureRecognizer?.isEnabled == true
        let isThereStackedViewControllers = viewControllers.count > 1
        return isSystemSwipeToBackEnabled && isThereStackedViewControllers
    }
}
rshev
  • 4,086
  • 1
  • 23
  • 32