简体   繁体   English

像Instagram iPhone应用程序一样向右滑动导航弹出视图。我如何实现这一目标?

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

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.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

This single line of code for pop navigation view and it's a work for me but when i swipe form middle of screen this will not work like Instagram iPhone app. 这个单行代码用于流行导航视图,它对我来说很有用,但是当我在屏幕中间滑动时,这将无法像Instagram iPhone应用程序那样工作。

Here i give a one screen of Instagram app in that you can see the Example of swipe right pop navigation view: 在这里,我给出了一个Instagram应用程序的屏幕,你可以看到滑动右侧弹出导航视图的示例:

在此输入图像描述

Apple's automatic implementation of the "swipe right to pop VC" only works for the left ~20 points of the screen. Apple自动实现“刷卡直播VC”仅适用于屏幕左侧~20点。 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. 想象一下,你在屏幕上有一个UIScrollView ,你无法向右滑动,因为它不断弹出VC。 This wouldn't be nice. 这不会很好。

Apple says here : Apple 在这里说:

interactivePopGestureRecognizer interactivePopGestureRecognizer

The gesture recognizer responsible for popping the top view controller off the navigation stack. 手势识别器负责将顶视图控制器弹出导航堆栈。 (read-only) (只读)

@property(nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer @property(非原子,只读)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 . 因此,您必须实现自己的UIGestureRecognizer ,并将其行为与UIViewControllerinteractivePopGestureRecognizer


Edit : 编辑:

Here is a solution I built. 这是我建立的解决方案。 You can implement your own transition conforming to the UIViewControllerAnimatedTransitioning delegate. 您可以实现符合UIViewControllerAnimatedTransitioning委托的自己的转换。 This solution works , but has not been thoroughly tested. 此解决方案有效 ,但尚未经过全面测试。

You will get an interactive sliding transition to pop your ViewControllers. 您将获得一个交互式滑动过渡以弹出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 UINavigationController> ViewController> SecondViewController

CustomPopTransition.h : CustomPopTr​​ansition.h

#import <Foundation/Foundation.h>

@interface CustomPopTransition : NSObject <UIViewControllerAnimatedTransitioning>

@end

CustomPopTransition.m : CustomPopTr​​ansition.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 : SecondViewController.h

#import <UIKit/UIKit.h>

@interface SecondViewController : UIViewController <UINavigationControllerDelegate>

@end

SecondViewController.m : 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

Here's a Swift version of Spynet's answer, with a few modifications. 这是一个Swift版本的Spynet的答案,只有一些修改。 Firstly, I've defined a linear curve for the UIView animation. 首先,我为UIView动画定义了一条线性曲线。 Secondly, I've added a semi-transparent black background to the view underneath for a better effect. 其次,我在下面的视图中添加了一个半透明的黑色背景,以获得更好的效果。 Thirdly, I've subclassed a UINavigationController . 第三,我已经将UINavigationController子类化了。 This allows the transition to be applied to any "Pop" transition within the UINavigationController. 这允许将转换应用于UINavigationController中的任何“Pop”转换。 Here's the code: 这是代码:

CustomPopTransition.swift CustomPopTr​​ansition.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 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: 结果示例: 在此输入图像描述

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 . 真的是没有必要推出这个自己的解决方案,子类UINavigationController和引用内置手势可以作为解释就好了这里

The same solution in Swift: 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: 如果这样做,还要实现以下UINavigationControllerDelegate函数以避免在根视图控制器上出现奇怪的行为:

public func navigationController(_: UINavigationController,
                                 didShow _: UIViewController, animated _: Bool) {
  self.fullScreenPanGestureRecognizer.isEnabled = self.viewControllers.count > 1
}

Subclassing the UINavigationController you can add a UISwipeGestureRecognizer to trigger the pop action: UINavigationController子类化,您可以添加UISwipeGestureRecognizer来触发弹出操作:

.h file: .h文件:

#import <UIKit/UIKit.h>

@interface CNavigationController : UINavigationController

@end

.m file: .m文件:

#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

i achieved this by using this library https://github.com/ykyouhei/KYDrawerController i changed width of screen to _drawerWidth = self.view.bounds.size.width; 我通过使用这个库实现了这个https://github.com/ykyouhei/KYDrawerController我将屏幕宽度更改为_drawerWidth = self.view.bounds.size.width; which worked as i wanted 这是我想要的

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM