Apple's automatic implementation of the "swipe right to pop VC" only works for the left ~20 points of the screen. This way, they make sure they don't mess with your app's functionalities. Imagine you have a UIScrollView
on screen, and you can't swipe right because it keeps poping VCs out. This wouldn't be nice.
Apple says here :
interactivePopGestureRecognizer
The gesture recognizer responsible for popping the top view controller off the navigation stack. (read-only)
@property(nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer
The navigation controller installs this gesture recognizer on its view
and uses it to pop the topmost view controller off the navigation
stack. You can use this property to retrieve the gesture recognizer
and tie it to the behavior of other gesture recognizers in your user
interface. When tying your gesture recognizers together, make sure
they recognize their gestures simultaneously to ensure that your
gesture recognizers are given a chance to handle the event.
So you will have to implement your own UIGestureRecognizer
, and tie its behavior to the interactivePopGestureRecognizer
of your UIViewController
.
Edit :
Here is a solution I built. You can implement your own transition conforming to the UIViewControllerAnimatedTransitioning
delegate. This solution works, but has not been thoroughly tested.
You will get an interactive sliding transition to pop your ViewControllers. You can slide to right from anywhere in the view.
Known issue : if you start the pan and stop before half the width of the view, the transition is canceled (expected behavior). During this process, the views reset to their original frames. Their is a visual glitch during this animation.
The classes of the example are the following :
UINavigationController > ViewController > SecondViewController
CustomPopTransition.h :
#import <Foundation/Foundation.h>
@interface CustomPopTransition : NSObject <UIViewControllerAnimatedTransitioning>
@end
CustomPopTransition.m :
#import "CustomPopTransition.h"
#import "SecondViewController.h"
#import "ViewController.h"
@implementation CustomPopTransition
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.3;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
SecondViewController *fromViewController = (SecondViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
ViewController *toViewController = (ViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toViewController.view];
[containerView bringSubviewToFront:fromViewController.view];
// Setup the initial view states
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
[UIView animateWithDuration:0.3 animations:^{
fromViewController.view.frame = CGRectMake(toViewController.view.frame.size.width, fromViewController.view.frame.origin.y, fromViewController.view.frame.size.width, fromViewController.view.frame.size.height);
} completion:^(BOOL finished) {
// Declare that we've finished
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
@end
SecondViewController.h :
#import <UIKit/UIKit.h>
@interface SecondViewController : UIViewController <UINavigationControllerDelegate>
@end
SecondViewController.m :
#import "SecondViewController.h"
#import "ViewController.h"
#import "CustomPopTransition.h"
@interface SecondViewController ()
@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactivePopTransition;
@end
@implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.delegate = self;
UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePopRecognizer:)];
[self.view addGestureRecognizer:popRecognizer];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// Stop being the navigation controller's delegate
if (self.navigationController.delegate == self) {
self.navigationController.delegate = nil;
}
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
// Check if we're transitioning from this view controller to a DSLSecondViewController
if (fromVC == self && [toVC isKindOfClass:[ViewController class]]) {
return [[CustomPopTransition alloc] init];
}
else {
return nil;
}
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
// Check if this is for our custom transition
if ([animationController isKindOfClass:[CustomPopTransition class]]) {
return self.interactivePopTransition;
}
else {
return nil;
}
}
- (void)handlePopRecognizer:(UIPanGestureRecognizer*)recognizer {
// Calculate how far the user has dragged across the view
CGFloat progress = [recognizer translationInView:self.view].x / (self.view.bounds.size.width * 1.0);
progress = MIN(1.0, MAX(0.0, progress));
if (recognizer.state == UIGestureRecognizerStateBegan) {
NSLog(@"began");
// Create a interactive transition and pop the view controller
self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
[self.navigationController popViewControllerAnimated:YES];
}
else if (recognizer.state == UIGestureRecognizerStateChanged) {
NSLog(@"changed");
// Update the interactive transition's progress
[self.interactivePopTransition updateInteractiveTransition:progress];
}
else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) {
NSLog(@"ended/cancelled");
// Finish or cancel the interactive transition
if (progress > 0.5) {
[self.interactivePopTransition finishInteractiveTransition];
}
else {
[self.interactivePopTransition cancelInteractiveTransition];
}
self.interactivePopTransition = nil;
}
}
@end