26

In my app I have different controllers. When I push controller1 to navigation controller and swipe to back, all works good. But, if I push navigation controller1, and into controller1 push controller2 and try to swipe to back I get a frozen application. If go back through back button all works fine.

How can I catch the problem?

Dania Delbani
  • 816
  • 1
  • 11
  • 27
Mike
  • 750
  • 2
  • 10
  • 14
  • When it freezes, what is shown if you pause the app with the debugger? Any log / exception? – Wain Jan 19 '14 at 20:13
  • 1
    Your problem most probably lies in the `viewWillDisappear` of the popping VC, and/or the `viewWillAppear` of the VC you are popping to. Share some code , if possible. – n00bProgrammer Jan 29 '14 at 07:40

9 Answers9

40

I had similar problem with freezing interface when using swipe-to-pop gesture. In my case the problem was in controller1.viewDidAppear I was disabling swipe gesture: self.navigationController.interactivePopGestureRecognizer.enabled = NO. So when user started to swipe back from contorller2, controller1.viewDidAppear was triggered and gesture was disabled, right during it's work.

I solved this by setting self.navigationController.interactivePopGestureRecognizer.delegate = self in controller1 and implementing gestureRecognizerShouldBegin:, instead of disabling gesture recognizer:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
            gestureRecognizer == self.navigationController.interactivePopGestureRecognizer) {
        return NO;
    }
    return YES;
}
glyuck
  • 3,367
  • 18
  • 14
  • 2
    I did similar solution - in viewWillAppear set flag to False and in viewDidAppear setFlag to True. In gestureRecognizerShouldBegin I check this value on True to enable gesture. Your solution looks more elegant, I'll try do it and will say result. Thanks for reply – Mike Feb 01 '14 at 10:30
  • If you're not setting `self.navigationController.interactivePopGestureRecognizer.delegate = self` in `controller2`, `controller1` will get delegate calls after pushing `controller2`. – derpoliuk Feb 28 '14 at 11:30
  • I went with subclassing UINavigationController and implementing it's delegate to enable/disable the recognizer here: http://stackoverflow.com/a/41619830/954643 – qix Jan 12 '17 at 17:58
19

My solution was to add a delegate to the navigation controller. Then disable the pop gesture recogniser in the root view controller only. YMMV.

#pragma mark - UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    BOOL isRootVC = viewController == navigationController.viewControllers.firstObject;
    navigationController.interactivePopGestureRecognizer.enabled = !isRootVC;
}
Ryan
  • 5,416
  • 1
  • 39
  • 36
  • 2
    I've spent hours looking into why app was hanging, you sir are amazing, thanks for the most elegant answer! Fixed the hang. As soon as I pushed a VC that became the interactive vc delegate, when I went back to root vc, and would swipe from left edge and then push a vc again, would hang every time, this fixes it! – Kevin Mar 18 '15 at 04:27
  • That doesn't work as you can still cause a freeze when you are swiping during animation is happening. – Grzegorz Krukowski Oct 07 '19 at 14:38
  • Does not work if you want any child VCs to have swipe navigation disabled. – jcharch Feb 24 '20 at 20:48
6

I had problem when swipe back on first controller and then tap on tableViewCell, I think that force touch swipe from edge is registering swipe back before switches app, sometimes UI get stuck, sometimes when I swipe back it starts entering on destination controller. I solved problem with this extension, it worked for me and it is simple solution. Swift 4.2

extension UINavigationController:UINavigationControllerDelegate {

    open override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }

    public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        if responds(to: #selector(getter: self.interactivePopGestureRecognizer)) {
            if viewControllers.count > 1 {
                interactivePopGestureRecognizer?.isEnabled = true
            } else {
                interactivePopGestureRecognizer?.isEnabled = false
            }
        }
    }
}
Bhaumik
  • 1,218
  • 1
  • 11
  • 20
Sakule
  • 61
  • 1
  • 5
3

Swift 4:

Set the delegate,

self.navigationController?.interactivePopGestureRecognizer?.delegate = self

Implement the delegate method,

extension YourVC: UIGestureRecognizerDelegate{
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer && conditionToDisableTheGesture {
            return false
        }else{
            return true
        }
    }

}
cnu
  • 443
  • 4
  • 9
  • What `conditionToDisableTheGesture` does mean in your condition statement? – Alena Mar 11 '19 at 15:09
  • @Alena conditionToDisableTheGesture means under what condition(if any), you want to disable this gesture. In my case, I need to disable this gesture only for a particular scenario. – cnu Mar 12 '19 at 02:31
0

I had same issue and I found below solution. add below controller

#import <UIKit/UIKit.h>
@interface CBNavigationController : UINavigationController     <UIGestureRecognizerDelegate,UINavigationControllerDelegate>
@end

#import "CBNavigationController.h"
@interface CBNavigationController ()
@end
@implementation CBNavigationController
- (void)viewDidLoad
{
NSLog(@"%s",__FUNCTION__);
__weak CBNavigationController *weakSelf = self;

if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
{
    self.interactivePopGestureRecognizer.delegate = weakSelf;
    self.delegate = weakSelf;
}
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
NSLog(@"%s",__FUNCTION__);

if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = NO;

[super pushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController
   didShowViewController:(UIViewController *)viewController
                animated:(BOOL)animate
{
NSLog(@"%s",__FUNCTION__);

// Enable the gesture again once the new controller is shown

if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = YES;
}
@end

Can refer below link

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

sinh99
  • 3,909
  • 32
  • 32
0

My solution is exchange self.navigationController.interactivePopGestureRecognizer.delegate between selfImplementDelegate and SystemDelegate

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [_tableView reloadData];
    _oldReturnDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
    self.navigationController.interactivePopGestureRecognizer.delegate = self;
}

- (void)viewWillDisappear:(BOOL)animated
{
    self.navigationController.interactivePopGestureRecognizer.delegate = _oldReturnDelegate;
    [super viewWillDisappear:animated];
}
David Ansermot
  • 6,052
  • 8
  • 47
  • 82
Edward
  • 1
  • This is actually the correct root cause of this problem. the delegate being set by user is overriding the system's default navigation delegate. Setting back to nil is not sufficient. But I wouldn't use this solution in production due to nasty global state. – AmitP Jan 16 '17 at 11:45
0

I would suggest you to try this. This works perfectly for me. You can still enjoy Interactive swipe.

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
      gestureRecognizer == self.navigationController.interactivePopGestureRecognizer) {
    if(self.navigationController.viewControllers.count<=1)
    {
      return NO;
    }
  }
  return YES;
}
Kaey
  • 4,615
  • 1
  • 14
  • 18
0

Swift 4

Add this code to root navigation controller

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return self == self.navigationController?.topViewController ? false : true
}

Add UIGestureRecognizerDelegate protocol

self.navigationController?.interactivePopGestureRecognizer?.delegate = self
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
Community
  • 1
  • 1
-2

I solved my problem by UINavigationController interactivePopGestureRecognizer working abnormal in iOS7 and set self.navigationController.interactivePopGestureRecognizer.delegate = self; on every viewcontroller's - (void)viewWillAppear:(BOOL)animated

Community
  • 1
  • 1