Uinavigationcontroller Interactivepopgesturerecognizer Working Abnormal in iOS7

UINavigationController interactivePopGestureRecognizer working abnormal in iOS7

This code can wroks well for me.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [gestureRecognizer isKindOfClass:UIScreenEdgePanGestureRecognizer.class];
}

InteractivePopGestureRecognizer causing app freezing

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;
}

In iOS7, abnormal popping action of UINavigationController with edge swiping

I found a solution to this problem.

What I did was in my UINagivationController class, set a BOOL property called

@property (nonatomic, assign) BOOL interactivePopGestureComplete;

and then in init of that UINagivationController

self.interactivePopGestureComplete = YES;
if([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
[self.interactivePopGestureRecognizer addTarget:self action:@selector(defaultGestureAction:)];

and the action method like this:

- (void)defaultGestureAction:(UIGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateChanged)
self.interactivePopGestureComplete = NO;
else
self.interactivePopGestureComplete = YES;
}

and in viewWillAppear: or viewWillLayoutSubviews: (in the viewControllers where there is this problem), wherever in these two methods you are updating the navigationBarButtons and the navigationBar, just check if

// Lets say our UINagivationController class name is NavigationCon
NavigationCon *navCon = (NavigationCon *)self.navigationController;
if(navCon.interactivePopGestureComplete)
{
// only then update navigationBar
}

How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem?

First set delegate in viewDidLoad:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

And then disable gesture when pushing:

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
[super pushViewController:viewController animated:animated];
self.interactivePopGestureRecognizer.enabled = NO;
}

And enable in viewDidDisappear:

self.navigationController.interactivePopGestureRecognizer.enabled = YES;

Also, add UINavigationControllerDelegate to your view controller.

Canceling interactive UINavigationController pop gesture does not call UINavigationControllerDelegate methods

For anyone interested, I have found 2 ways to work around this at the UINavigationControllerDelegate level.

  1. Use KVO to observe the state property of the interactivePopGestureRecognizer. Unfortunately, canceling the transition does not change the state to UIGestureRecognizerStateFailed but instead just UIGestureRecognizerStateEnded, so you would need to write a bit of additional code to keep track of what happened if you needed to discern between a cancelled or completed pop.

  2. After testing it, this is probably the better solution: Use the navigationController:willShowViewController:animated: method to add a notification block to the transition coordinator. It looks something like this:

    - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
    [[self transitionCoordinator] notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context)
    {
    if([context isCancelled])
    {
    UIViewController *fromViewController = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
    [self navigationController:navigationController willShowViewController:fromViewController animated:animated];

    if([self respondsToSelector:@selector(navigationController:didShowViewController:animated:)])
    {
    NSTimeInterval animationCompletion = [context transitionDuration] * (double)[context percentComplete];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((uint64_t)animationCompletion * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self navigationController:navigationController didShowViewController:fromViewController animated:animated];
    });
    }

    }
    }];
    }

I was hesitant to use this solution at first because the documentation was unclear about whether or not you could set more than one of these (since in that case, if an unknowing view controller also set its own notification block, it could potentially either replace this one or get replaced by this one). After testing it though, it appears that it is not a 1:1 relationship and you can add multiple notification blocks safely.

edit

I edited the code above to delay the navigationController:didShowViewController:animated: call to only be called when the animation is supposed to be completed to more closely match the expected default behavior.

How can I implement drag right to dismiss a View Controller that's in a navigation stack?

Sample Image

Made a demo project in Github
https://github.com/rishi420/SwipeRightToPopController

I've used UIViewControllerAnimatedTransitioning protocol

From the doc:

// This is used for percent driven interactive transitions, as well as for container controllers ...

Added a UIPanGestureRecognizer to the controller's view. This is the action of the gesture:

func handlePanGesture(panGesture: UIPanGestureRecognizer) {

let percent = max(panGesture.translationInView(view).x, 0) / view.frame.width

switch panGesture.state {

case .Began:
navigationController?.delegate = self
navigationController?.popViewControllerAnimated(true)

case .Changed:
percentDrivenInteractiveTransition.updateInteractiveTransition(percent)

case .Ended:
let velocity = panGesture.velocityInView(view).x

// Continue if drag more than 50% of screen width or velocity is higher than 1000
if percent > 0.5 || velocity > 1000 {
percentDrivenInteractiveTransition.finishInteractiveTransition()
} else {
percentDrivenInteractiveTransition.cancelInteractiveTransition()
}

case .Cancelled, .Failed:
percentDrivenInteractiveTransition.cancelInteractiveTransition()

default:
break
}
}

Steps:

  1. Calculate the percentage of drag on the view
  2. .Begin: Specify which segue to perform and assign UINavigationController delegate. delegate will be needed for InteractiveTransitioning
  3. .Changed: UpdateInteractiveTransition with percentage
  4. .Ended: Continue remaining transitioning if drag 50% or more or higher velocity else cancel
  5. .Cancelled, .Failed: cancel transitioning


References:

  1. UIPercentDrivenInteractiveTransition
  2. https://github.com/visnup/swipe-left
  3. https://github.com/robertmryan/ScreenEdgeGestureNavigationController
  4. https://github.com/groomsy/custom-navigation-animation-transition-demo


Related Topics



Leave a reply



Submit