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

Posted

技术标签:

【中文标题】像 Instagram iPhone 应用程序一样向右滑动时导航弹出视图。我如何实现这一点?【英文标题】:Navigation pop view when swipe right like Instagram iPhone app.How i achieve this? 【发布时间】:2014-03-07 08:04:03 【问题描述】:

我想在屏幕上向右滑动时弹出一个视图,或者它就像导航栏的后退按钮一样工作。

我正在使用:

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

弹出导航视图的这一行代码对我来说是一项工作,但是当我在屏幕中间滑动时,这不会像 Instagram iPhone 应用程序那样工作。

在这里我给出一个 Instagram 应用程序的屏幕,您可以看到向右滑动弹出导航视图的示例:

【问题讨论】:

你应该关注这个问题(它还没有答案,但它是重复的):***.com/questions/20714595/… 带代码和解释的完美解决方案:- ***.com/a/32990248/988169 检查这些存储库:github.com/rishi420/SwipeRightToPopController/blob/master/… 和 github.com/fastred/SloppySwiper/blob/master/Classes/… 【参考方案1】:

Apple 自动实现的“向右滑动弹出 VC”仅适用于屏幕左侧约 20 点。通过这种方式,他们可以确保不会干扰您的应用程序的功能。想象一下,你在屏幕上有一个UIScrollView,你不能向右滑动,因为它会不断弹出 VC。这不会很好。

苹果说here:

interactivePopGestureRecognizer

负责将顶视图控制器从导航堆栈中弹出的手势识别器。 (只读)

@property(nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer

导航控制器在其视图上安装此手势识别器 并使用它从导航中弹出最顶层的视图控制器 堆。您可以使用此属性来检索手势识别器 并将其与用户中其他手势识别器的行为联系起来 界面。将手势识别器捆绑在一起时,请确保 他们同时识别他们的手势,以确保您的 手势识别器有机会处理该事件。

因此您必须实现自己的UIGestureRecognizer,并将其行为与UIViewControllerinteractivePopGestureRecognizer 联系起来。


编辑:

这是我构建的解决方案。您可以实现符合UIViewControllerAnimatedTransitioning 委托的自己的转换。此解决方案有效,但尚未经过彻底测试。

您将获得一个交互式滑动过渡来弹出您的 ViewController。您可以从视图中的任意位置向右滑动。

已知问题:如果您在视图宽度的一半之前开始平移并停止,则会取消过渡(预期行为)。在此过程中,视图将重置为其原始帧。他们是这个动画中的一个视觉故障。

示例的类如下:

UINavigationController > ViewController > SecondViewController

CustomPopTr​​ansition.h

#import <Foundation/Foundation.h>

@interface CustomPopTransition : NSObject <UIViewControllerAnimatedTransitioning>

@end

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

#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

【讨论】:

任何想法我该怎么做? @chetu : 不,对不起.. 如果我找到办法,我会告诉你,但你最好的选择是自己尝试.. github.com/vinqon/MultiLayerNavigation/blob/master/Src/… 这将起作用,但限制是,当我们使用幻灯片视图控制器时,它不起作用。 Ex-pop(后退)不工作。 @dipang 非常感谢,切图。绝妙的解决方案! (多层导航) 在 9.0 上完美运行。谢谢你。我也没有看到你提到的视觉故障。【参考方案2】:

创建一个平移手势识别器并移动交互式弹出手势识别器的目标。

将您的识别器添加到推送的视图控制器的 viewDidLoad 中,瞧!

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)

【讨论】:

我笑了。太精彩了。很好的答案。 谢谢,这里有详细的解决方案***.com/a/57487724/1745000【参考方案3】:

这是 Spynet 的答案的 Swift 版本,有一些修改。首先,我为UIView 动画定义了一条线性曲线。其次,我在下面的视图中添加了一个半透明的黑色背景以获得更好的效果。第三,我对UINavigationController 进行了子类化。这允许将转换应用于 UINavigationController 中的任何“Pop”转换。代码如下:

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

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
        
    

结果示例:

【讨论】:

@Spynet 没问题!我刚刚更新了 CustomPopTr​​ansition.swift 代码,因为我注意到此处的其他代码存在一个错误,这与您在承诺滑回之前多次拖动然后释放时发生的情况有关... 干得好,继续努力【参考方案4】:

确实没有必要为此推出您自己的解决方案,子类化UINavigationController 并引用内置手势就可以正常工作,正如here 所解释的那样。

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
  ()

如果你这样做,还要实现以下UINavigationControllerDelegate 函数以避免根视图控制器出现奇怪的行为:

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

【讨论】:

不错!但动画有点奇怪。看起来像视图控制器,您将其拖动到没有动画的最终位置。例如,在 instagram 或电报 ios 应用程序中,它更流畅,从 ~20-30pt 开始动画到最终位置,并检查滑动方向是否改变,然后平滑地将动画视图动画到初始位置。 + 我也不喜欢的 - 看起来你的代码有点 hacky;看起来您正在调用可以被苹果删除的私有方法。但我投了赞成票:) 你实现了答案的第二部分吗?我必须这样做以避免动画出现任何奇怪的行为。我听说你在私有方法上,但我已经用这个代码向商店提交了一个应用程序,它工作得很好。还要检查答案中的链接以获取此解决方案的原始描述(这不是我的想法:)) 啊,我看到链接对于解决方案的来源不再有效。 谢谢!)这个解决方案是其他解决方案中最好的,也是短而通用的,因为它与导航控制器一起工作,而不是与 UIViewController 一起工作。【参考方案5】:

继承UINavigationController,您可以添加UISwipeGestureRecognizer 来触发弹出动作:

.h 文件:

#import <UIKit/UIKit.h>

@interface CNavigationController : UINavigationController

@end

.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

【讨论】:

将您的代码粘贴到您的答案中。如果您的链接失效,您的回答将毫无用处。 此解决方案使用滑动,因此您无法控制过渡。它不会是交互式的,而 Apple 的实现是。 @rdurand 我需要支持我喜欢的 ios6 设备 在这种情况下这是一个可以接受的解决方案。我在答案中添加了一个有效的 iOS7+ 解决方案。 @rdurand 现在可以了吗

以上是关于像 Instagram iPhone 应用程序一样向右滑动时导航弹出视图。我如何实现这一点?的主要内容,如果未能解决你的问题,请参考以下文章

如何为instagram中的图像赋予效果[关闭]

如何创建像 Instagram 应用一样的动画背景渐变?

如何使用单个 UITableView 设计像 instagram 一样的时间线?

像Instagram这样的照片效果[关闭]

如何像 instagram 一样快速加载图像

当我们上传图像时,是不是有任何反应原生包可以像 instagram 一样自动裁剪图像?