Navigation pop view when swipe right like Instagram iPhone app.How i achieve this?

前端 未结 5 864
有刺的猬
有刺的猬 2020-12-02 10:48

I want to pop a view when swipe right on screen or it\'s work like back button of navigation bar.

I am using:

self.navigationController.interactivePo         


        
相关标签:
5条回答
  • 2020-12-02 11:10

    Here's a Swift version of Spynet's answer, with a few modifications. Firstly, I've defined a linear curve for the UIView animation. Secondly, I've added a semi-transparent black background to the view underneath for a better effect. Thirdly, I've subclassed a UINavigationController. This allows the transition to be applied to any "Pop" transition within the UINavigationController. Here's the code:

    CustomPopTransition.swift

    import UIKit
    
    class CustomPopTransition: NSObject, UIViewControllerAnimatedTransitioning {
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.3
        }
    
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
                let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
                else {
                    return
            }
    
            let containerView = transitionContext.containerView
            containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)
    
            // Setup the initial view states
            toViewController.view.frame = CGRect(x: -100, y: toViewController.view.frame.origin.y, width: fromViewController.view.frame.size.width, height: fromViewController.view.frame.size.height)
    
            let dimmingView = UIView(frame: CGRect(x: 0,y: 0, width: toViewController.view.frame.width, height: toViewController.view.frame.height))
            dimmingView.backgroundColor = UIColor.black
            dimmingView.alpha = 0.5
    
            toViewController.view.addSubview(dimmingView)
    
            UIView.animate(withDuration: transitionDuration(using: transitionContext),
                           delay: 0,
                           options: UIView.AnimationOptions.curveLinear,
                           animations: {
                            dimmingView.alpha = 0
                            toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
                            fromViewController.view.frame = CGRect(x: toViewController.view.frame.size.width, y: fromViewController.view.frame.origin.y, width: fromViewController.view.frame.size.width, height: fromViewController.view.frame.size.height)
            },
                           completion: { finished in
                            dimmingView.removeFromSuperview()
                            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
            )
        }
    }
    

    PoppingNavigationController.swift

    import UIKit
    
    class PoppingNavigationController : UINavigationController, UINavigationControllerDelegate {
        var interactivePopTransition: UIPercentDrivenInteractiveTransition!
    
        override func viewDidLoad() {
            self.delegate = self
        }
    
        func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
            addPanGesture(viewController: viewController)
        }
    
        func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            if (operation == .pop) {
                return CustomPopTransition()
            }
            else {
                return nil
            }
        }
    
        func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            if animationController.isKind(of: CustomPopTransition.self) {
                return interactivePopTransition
            }
            else {
                return nil
            }
        }
    
        func addPanGesture(viewController: UIViewController) {
            let popRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanRecognizer(recognizer:)))
            viewController.view.addGestureRecognizer(popRecognizer)
        }
    
        @objc
        func handlePanRecognizer(recognizer: UIPanGestureRecognizer) {
            // Calculate how far the user has dragged across the view
            var progress = recognizer.translation(in: self.view).x / self.view.bounds.size.width
            progress = min(1, max(0, progress))
            if (recognizer.state == .began) {
                // Create a interactive transition and pop the view controller
                self.interactivePopTransition = UIPercentDrivenInteractiveTransition()
                self.popViewController(animated: true)
            }
            else if (recognizer.state == .changed) {
                // Update the interactive transition's progress
                interactivePopTransition.update(progress)
            }
            else if (recognizer.state == .ended || recognizer.state == .cancelled) {
                // Finish or cancel the interactive transition
                if (progress > 0.5) {
                    interactivePopTransition.finish()
                }
                else {
                    interactivePopTransition.cancel()
                }
                interactivePopTransition = nil
            }
        }
    }
    

    Example of the result:

    0 讨论(0)
  • 2020-12-02 11:16

    There really is no need to roll your own solution for this, sub-classing UINavigationController and referencing the built-in gesture works just fine as explained here.

    The same solution in Swift:

    public final class MyNavigationController: UINavigationController {
    
      public override func viewDidLoad() {
        super.viewDidLoad()
    
    
        self.view.addGestureRecognizer(self.fullScreenPanGestureRecognizer)
      }
    
      private lazy var fullScreenPanGestureRecognizer: UIPanGestureRecognizer = {
        let gestureRecognizer = UIPanGestureRecognizer()
    
        if let cachedInteractionController = self.value(forKey: "_cachedInteractionController") as? NSObject {
          let string = "handleNavigationTransition:"
          let selector = Selector(string)
          if cachedInteractionController.responds(to: selector) {
            gestureRecognizer.addTarget(cachedInteractionController, action: selector)
          }
        }
    
        return gestureRecognizer
      }()
    }
    

    If you do this, also implement the following UINavigationControllerDelegate function to avoid strange behaviour at the root view controller:

    public func navigationController(_: UINavigationController,
                                     didShow _: UIViewController, animated _: Bool) {
      self.fullScreenPanGestureRecognizer.isEnabled = self.viewControllers.count > 1
    }
    
    0 讨论(0)
  • 2020-12-02 11:28

    Create a pan gesture recogniser and move the interactive pop gesture recogniser's targets across.

    Add your recogniser to the pushed view controller's viewDidLoad and voila!

    let popGestureRecognizer = self.navigationController!.interactivePopGestureRecognizer!
    if let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray {
      let gestureRecognizer = UIPanGestureRecognizer()
      gestureRecognizer.setValue(targets, forKey: "targets")
      self.view.addGestureRecognizer(gestureRecognizer)
    }
    
    0 讨论(0)
  • 2020-12-02 11:30

    Subclassing the UINavigationController you can add a UISwipeGestureRecognizer to trigger the pop action:

    .h file:

    #import <UIKit/UIKit.h>
    
    @interface CNavigationController : UINavigationController
    
    @end
    

    .m file:

    #import "CNavigationController.h"
    
    @interface CNavigationController ()<UIGestureRecognizerDelegate, UINavigationControllerDelegate>
    
    @property (nonatomic, retain) UISwipeGestureRecognizer *swipeGesture;
    
    @end
    
    @implementation CNavigationController
    
    #pragma mark - View cycles
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        __weak CNavigationController *weakSelf = self;
        self.delegate = weakSelf;
    
        self.swipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(gestureFired:)];
        [self.view addGestureRecognizer:self.swipeGesture]; }
    
    #pragma mark - gesture method
    
    -(void)gestureFired:(UISwipeGestureRecognizer *)gesture {
        if (gesture.direction == UISwipeGestureRecognizerDirectionRight)
        {
            [self popViewControllerAnimated:YES];
        } }
    
    #pragma mark - UINavigation Controller delegate
    
    - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
        self.swipeGesture.enabled = NO;
        [super pushViewController:viewController animated:animated]; }
    
    #pragma mark UINavigationControllerDelegate
    
    - (void)navigationController:(UINavigationController *)navigationController
           didShowViewController:(UIViewController *)viewController
                        animated:(BOOL)animate {
        self.swipeGesture.enabled = YES; }
    
    @end
    
    0 讨论(0)
  • 2020-12-02 11:31

    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
    
    0 讨论(0)
提交回复
热议问题