像 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
,并将其行为与UIViewController
的interactivePopGestureRecognizer
联系起来。
编辑:
这是我构建的解决方案。您可以实现符合UIViewControllerAnimatedTransitioning
委托的自己的转换。此解决方案有效,但尚未经过彻底测试。
您将获得一个交互式滑动过渡来弹出您的 ViewController。您可以从视图中的任意位置向右滑动。
已知问题:如果您在视图宽度的一半之前开始平移并停止,则会取消过渡(预期行为)。在此过程中,视图将重置为其原始帧。他们是这个动画中的一个视觉故障。
示例的类如下:
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
【讨论】:
任何想法我该怎么做? @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”转换。代码如下:
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
结果示例:
【讨论】:
@Spynet 没问题!我刚刚更新了 CustomPopTransition.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 应用程序一样向右滑动时导航弹出视图。我如何实现这一点?的主要内容,如果未能解决你的问题,请参考以下文章