0

Required:

I want to enable iOS7 swipe to back feature with custom navigation back button item.

Current Implementation:

After researching a lot, I found the following solution to be best:

  1. Set the delegate of the gesture recognizer as follows

    self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
    
  2. This, creates a lot of bugs as mentioned in this stackoverflow answer. To avoid that, subclassing the UINavigationController seems to be the only feasible option. I did that as mentioned in this blog by Keighl.

Problem:

Basic swipe to back feature is working, but the strange thing is that, sometimes, the same viewController that is being dismissed, appears again after the pop action is completed.

i.e. suppose the navigation stack looks like A -> B. Popping B will again bring up B. This keeps on happening until eventually the viewController B actually gets dismissed and A appears.

This happens to all views in all viewController objects and not just to a specific one. Also, I have ensured that the push method is called only once at all places.

I also tried logging the navigation stack at each point, but there is only one instance of each viewController.

Point to note:

I need to disable the swipe feature in certain views. I did this by writing the code to disable and enable the swipe gesture in viewDidAppear and viewDidDisappear respectively.

Please provide your valuable suggestions or a solution to this problem. Thanks!

Community
  • 1
  • 1
GoGreen
  • 2,251
  • 1
  • 17
  • 29

1 Answers1

0

Short answer: You should add a UIScreenEdgePanGestureRecognizer to your view controller if you want to add a pop gesture where none exists. Modifying the existing interactivePopGestureRecognizer is probably not the right approach. Do this:

[self addGestureRecognizer:({
    UIScreenEdgePanGestureRecognizer *gesture =
        [[UIScreenEdgePanGestureRecognizer alloc]
        initWithTarget:self action:@selector(pop)];
    gesture;
})];

and

-(void)pop {
    // pop your view controller here
}

Long answer: Forcing the interactivePopGestureRecognizer.delegate is what breaks your code.

If you need to cast self as such:

self.navigationController.interactivePopGestureRecognizer.delegate =
    (id<UIGestureRecognizerDelegate>)self;

...it is because self is not a UIGestureRecognizerDelegate. The following should compile, link, build and run or you are setting yourself up for trouble:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

Note that being a UIGestureRecognizerDelegate specifically allows you to tweak a gesture's behavior at runtime, assuming you are implementing one of the following and ensuring that the tweak applies to a gesture you own:

  1. gestureRecognizerShouldBegin:
  2. gestureRecognizer:shouldReceiveTouch:
  3. gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
  4. gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
  5. gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:

By constantly changing the delegate of that interactivePopGestureRecognizer you did not create, all you are doing is preventing the iOS behavior to take place.

From the documentation

UINavigationController -interactivePopGestureRecognizer

The gesture recognizer responsible for popping the top view controller off the navigation stack. (read-only)

In plain English: Use this value is you need to combine that gesture with your own gesture. But you are not supposed to modify its behavior:

...You can use this property to retrieve the gesture recognizer and tie it to the behavior of other gesture recognizers in your user interface...

SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
  • Hi. Thanks for the detailed answer. Did you mean that it will work if I remove the cast to self? – GoGreen Jul 23 '14 at 17:14
  • Not quite. I believe you should not set `self.navigationController.interactivePopGestureRecognizer.delegate` at all. This going in the wrong direction. What is **not** working when you remove that line entirely? – SwiftArchitect Jul 23 '14 at 17:22
  • okay. See, I have custom left bar button items in my app. When I use them the default swipe gesture feature is gone. According to this [SO answer](http://stackoverflow.com/a/19076323/2954866), setting the delegate to self would enable it. – GoGreen Jul 23 '14 at 17:29
  • Do you have any other suggestion to enable swipe feature with custom leftBarButton items? – GoGreen Jul 23 '14 at 17:34
  • es. You should add a UIScreenEdgePanGestureRecognizer. I updated the answer. – SwiftArchitect Jul 23 '14 at 19:02
  • This approach was what I thought of first but I need to do this for all screens in an already existing project. That is why I went for changing the delegate. Now I think I am left with no choice but to create a base class first, and then add the `UIScreenEdgePanGestureRecognizer` in that class. Will accept your answer after I try it. – GoGreen Jul 24 '14 at 04:17
  • Super. Yes, code sweep is actually a good option. Consider a category instead of a subclass, or a 1 method utility. I will delete most comments after that, to reduce noise. – SwiftArchitect Jul 24 '14 at 20:16