App开发流程之右滑返回手势功能
iOS7以后,导航控制器,自带了从屏幕左边缘右滑返回的手势功能。
但是,如果自定义了导航栏返回按钮,这项功能就失效了,需要自行实现。又如果需要修改手势触发范围,还是需要自行实现。
广泛应用的一种实现方案是,采用私有变量和Api,完成手势交互和返回功能,自定义手势触发条件和额外功能。
另一种实现方案是,采用UINavigationController的代理方法实现交互和动画:
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
前者,特点是便捷,但是只能使用系统定义的交互和动画;后者,特点是高度自定义,但是需要额外实现交互协议和动画协议。
采用私有变量和Api,实现右滑返回手势功能。
先看最核心的逻辑:
- (void)base_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.base_panGestureRecognizer]) {
[self.interactivePopGestureRecognizer.view addGestureRecognizer:self.base_panGestureRecognizer]; //使用KVC获取私有变量和Api,实现系统原生的pop手势效果
NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
self.base_panGestureRecognizer.delegate = [self base_panGestureRecognizerDelegateObject];
[self.base_panGestureRecognizer addTarget:internalTarget action:internalAction]; self.interactivePopGestureRecognizer.enabled = NO;
} [self base_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController]; if (![self.viewControllers containsObject:viewController]) {
[self base_pushViewController:viewController animated:animated];
}
}
UINavigationController的interactivePopGestureRecognizer属性,是系统专用于将viewController弹出导航栈的手势识别对象,有一个私有变量名为“targets”,类似为NSArray;该数组第一个元素对象,有一个私有变量名为“target”,即为实现预期交互的对象;该对象有一个私有方法名为“handleNavigationTransition:”,即为目标方法。
在返回手势交互的UIView(self.interactivePopGestureRecognizer.view)上添加一个自定义的UIPanGestureRecognizer,利用其delegate对象实现的代理方法gestureRecognizerShouldBegin来控制手势生效条件。最后禁用系统的返回手势识别对象,就可以用自定义实现的pan手势来调用系统的pop交互和动画。
判断pan手势是否能触发返回操作的代码如下:
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
//正在转场
if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
return NO;
} //在导航控制器的根控制器界面
if (self.navigationController.viewControllers.count <= ) {
return NO;
} UIViewController *popedController = [self.navigationController.viewControllers lastObject]; if (popedController.base_popGestureDisabled) {
return NO;
} //满足有效手势范围
CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
CGFloat popGestureEffectiveDistanceFromLeftEdge = popedController.base_popGestureEffectiveDistanceFromLeftEdge; if (popGestureEffectiveDistanceFromLeftEdge >
&& beginningLocation.x > popGestureEffectiveDistanceFromLeftEdge) {
return NO;
} //右滑手势
UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint transition = [panGesture translationInView:panGesture.view]; if (transition.x <= ) {
return NO;
} return YES;
}
其中navigationController还使用了私有变量“_isTransitioning”,用于判断交互是否正在进行中。
为了使过场动画过程中,导航栏的交互动画自然,需要在UIViewController的viewWillAppear方法中,通过swizzle方法调用导航栏显示或隐藏的动画方法,所以需要增加一个延迟执行的代码块:
- (void)base_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
{
//如果navigationController不显示导航栏,直接return
if (self.navigationBarHidden) {
return;
} __weak typeof(self) weakSelf = self;
ViewControllerViewWillAppearDelayBlock block = ^(UIViewController *viewController, BOOL animated) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf setNavigationBarHidden:viewController.base_currentNavigationBarHidden animated:animated];
}
}; appearingViewController.viewWillAppearDelayBlock = block;
UIViewController *disappearingViewController = self.viewControllers.lastObject;
if (disappearingViewController && !disappearingViewController.viewWillAppearDelayBlock) {
disappearingViewController.viewWillAppearDelayBlock = block;
}
}
实现完整的分类的过程中,使用了一些运行时类型和方法。
1.引用头文件#import <objc/runtime.h>
2.为分类增加属性,涉及到了如下方法:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, const void *key)
例如UIViewController (PopGesture)中增加的属性base_currentNavigationBarHidden的get/set方法:
- (BOOL)base_currentNavigationBarHidden
{
NSNumber *number = objc_getAssociatedObject(self, _cmd);
if (number) {
return number.boolValue;
} self.base_currentNavigationBarHidden = NO;
return NO;
} - (void)setBase_currentNavigationBarHidden:(BOOL)hidden
{
self.canUseViewWillAppearDelayBlock = YES; objc_setAssociatedObject(self, @selector(base_currentNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
第一个参数一般为self。
第二个参数const void *key,要求传入一个地址。
可以声明一个static char *key;或者static NSString *key;,赋值与否并不重要,因为需要的只是地址,参数为&key。
而上述代码中使用了_cmd和@selector,作用是一样的。_cmd返回的是当前方法的SEL,@selector也是返回目标方法的SEL,即是函数地址。
第三个参数即是关联的值。
第四个参数policy,为枚举类型,基本对应属性引用相关的关键字:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = , /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = , /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = , /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = , /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
3.理解其他的一些运行时类型或方法
typedef struct objc_method *Method;//An opaque type that represents a method in a class definition.
Method class_getInstanceMethod(Class cls, SEL name) //返回实例方法
Method class_getClassMethod(Class cls, SEL name) //返回类方法
IMP method_getImplementation(Method m) //Returns the implementation of a method.
const char *method_getTypeEncoding(Method m) //Returns a string describing a method's parameter and return types.
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) //Adds a new method to a class with a given name and implementation.
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) //Replaces the implementation of a method for a given class.
void method_exchangeImplementations(Method m1, Method m2) //Exchanges the implementations of two methods.
以上方法,可以达到swizzle方法的目的,将分类中新增方法与已有旧的方法交换函数地址,可以作为完全替换(因为运行时,执行的方法名称仍然为viewWillAppear:,但是指向新增方法地址),也可以在新增方法代码中调用当前的方法名称(交换后,当前的方法名称指向旧方法地址)。例如UIViewController中的下列代码:
+(void)load
{
__weak typeof(self) weakSelf = self; static dispatch_once_t once;
dispatch_once(&once, ^{
[weakSelf swizzleOriginalSelector:@selector(viewWillAppear:) withNewSelector:@selector(base_viewWillAppear:)]; [weakSelf swizzleOriginalSelector:@selector(viewDidDisappear:) withNewSelector:@selector(base_viewDidDisappear:)];
});
} +(void)swizzleOriginalSelector:(SEL)originalSelector withNewSelector:(SEL)newSelector
{
Class selfClass = [self class]; Method originalMethod = class_getInstanceMethod(selfClass, originalSelector);
Method newMethod = class_getInstanceMethod(selfClass, newSelector); IMP originalIMP = method_getImplementation(originalMethod);
IMP newIMP = method_getImplementation(newMethod); //先用新的IMP加到原始SEL中
BOOL addSuccess = class_addMethod(selfClass, originalSelector, newIMP, method_getTypeEncoding(newMethod));
if (addSuccess) {
class_replaceMethod(selfClass, newSelector, originalIMP, method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, newMethod);
}
} -(void)base_viewWillAppear:(BOOL)animated
{
[self base_viewWillAppear:animated]; if (self.canUseViewWillAppearDelayBlock
&& self.viewWillAppearDelayBlock) {
self.viewWillAppearDelayBlock(self, animated);
} if (self.transitionCoordinator) {
[self.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
if ([context isCancelled]) {
self.base_isBeingPoped = NO;
}
}];
}
}
特别说明:
A.+load静态方法将在此分类加入运行时调用(Invoked whenever a class or category is added to the Objective-C runtime),执行顺序在该类自己的+load方法之后。
B.如果在使用中未明确设置base_currentNavigationBarHidden,canUseViewWillAppearDelayBlock则为NO,因为我封装的父类中提供了类似功能,所以不需要开启分类中同样的功能。该功能目的是提供给直接集成分类的朋友。
C.UIViewController的transitionCoordinator属性,在当前界面有过场交互时候,该属性有值。并且在交互结束时候,可以回调一个block,以告知过场交互的状态和相关属性。这里声明了一个属性base_isBeingPoped,用于标记当前视图控制器是否正在被pop出导航栈,如果交互取消了,置为NO,最终可以在viewDidDisappear:方法中判断并执行一些操作。
使用UINavigationController的代理方法来实现高度自定义的方案,下次再更新记录。
=================
已更新:http://www.cnblogs.com/ALongWay/p/5896982.html
base项目已更新:git@github.com:ALongWay/base.git
App开发流程之右滑返回手势功能的更多相关文章
- App开发流程之右滑返回手势功能续
上一篇记录了利用系统私有变量和方法实现右滑返回手势功能:http://www.cnblogs.com/ALongWay/p/5893515.html 这篇继续记录另一种方案:利用UINavigatio ...
- iOS开发之自定义导航栏返回按钮右滑返回手势失效的解决
我相信针对每一个iOS开发者来说~除了根视图控制器外~所有的界面通过导航栏push过去的界面都是可以通过右滑来返回上一个界面~其实~在很多应用和APP中~用户已经习惯了这个功能~然而~作为开发者的我们 ...
- 第二十六篇、因为自定item(nav)而使系统右滑返回手势失效的解决方法
@interface ViewController () <uigesturerecognizerdelegate> @end@implementation ViewController ...
- 解决右滑返回手势和UIScrollView中的手势冲突
当在一个viewController中添加了scrollView或者tableView的时候,贴边侧滑返回的时候会首先触发滚动而失效,要解决这个问题,需要通过requireGestureRecogni ...
- 关于iOS自定义返回按钮右滑返回手势失效的解决:
在viewDidLoad方法里面添加下面这一句代码即可 self.navigationController.interactivePopGestureRecognizer.delegate=(id)s ...
- 全新的手势,侧滑返回、全局右滑返回都OUT啦!
前言 Android快速开发框架-ZBLibrary 最近将以前的 全局右滑返回 手势功能改成了 底部左右滑动手势. 为什么呢?为了解决滑动返回手势的问题. 目前有3种滑动返回手势 一.侧滑返回 代表 ...
- iOS系统右滑返回全局控制方案
前言 今天有个小需求,在点击导航条上的返回按钮之前要调用某个API,并弹出UIAlertView来显示,根据用户的选项判断是否是返回还是继续留在当前控制器.举个简单的例子,当点击导航条上的左上角返回按 ...
- swift 关于FDFullscreenPopGesture的右滑返回
关于导航栏右滑返回的工具库 FDFullscreenPopGesture 是 OC 版本,用了 runtime 等各种骚操作 关于 swift ,我在 UINavigationController 的 ...
- iOS页面右滑返回的实现方法总结
1.边缘触发的系统方法 ①系统返回按钮 self.navigationController.interactivePopGestureRecognizer.enabled = YES; ②自定义返回 ...
随机推荐
- Struts2 源码分析——Hello world
新建第一个应用程序 上一章我们讲到了关于struts2核心机制.对于程序员来讲比较概念的一章.而本章笔者将会亲手写一个Hello world的例子.所以如果对struts2使用比较了解的朋友,请跳过本 ...
- JS魔法堂:元素克隆、剪切技术研究
一.前言 当需要新元素时我们可以通过 document.createElement 接口来创建一个全新的元素,也可以通过克隆已有元素的方式来获取一个新元素.而在部分浏览器中,通过复制来获取新元素的效率 ...
- 转自coolshell--vim的基本操作
开始前导语: 在正式转入python开发后,日常的工作中会和大量linux相关命令和工具接触,从另外一个层面,学习的东西相当的多,而VIM在整个的linux体系中所占据的角色就更不用说了,之前在处理g ...
- Python生成二维码脚本
简单的记录下二维码生成和解析的Python代码 依赖下面三个包: PIL(图像处理包,安装:pip install PIL) qrcode(二维码生成包,安装:pip install qrcode) ...
- ASP.NET MVC初识
最近在博客园看到了很多关于MVC的示例,自己打算写下来记录一下,如果有写得不对的地方,望大侠指出! 开始搭建项目 1. 建立Web项目 文件—>新建项目—>选择ASP.NET MVC4 W ...
- 基于MVC4+EasyUI的Web开发框架经验总结(1)-利用jQuery Tags Input 插件显示选择记录
最近花了不少时间在重构和进一步提炼我的Web开发框架上,力求在用户体验和界面设计方面,和Winform开发框架保持一致,而在Web上,我主要采用EasyUI的前端界面处理技术,走MVC的技术路线,在重 ...
- .net概念之程序集说明
一.程序集的一些基本概念: 程序集是包含一个或多个类型定义文件和资源文件的集合.它允许我们分离可重用类型的逻辑表示和物理表示. 程序集是一个可重用.可实施版本策略和安全策略的单元.它允许我们将类型和资 ...
- .Net 高效开发之不可错过的实用工具(转)
.Net 高效开发之不可错过的实用工具(转) 本文摘自: http://www.cnblogs.com/powertoolsteam/p/5240908.html#3372237 Visual Stu ...
- line-height 属性
p.small {line-height:90%} p.big {line-height:200%} 该属性会影响行框的布局.在应用到一个块级元素时,它定义了该元素中基线之间的最小距离而不是最 ...
- JavaScript深究系列 [一]
1. JavaScript中 = = = 首先,== equality 等同,=== identity 恒等. ==, 两边值类型不同的时候,要先进行类型转换,再比较. ===,不做类型转换,类型不同 ...