场景一 : 如果自定义了系统导航栏的返回按钮, 系统的左侧边缘返回手势将会失效
解决思路如下:
-
UINavigationController
的属性中, 系统的返回手势是 interactivePopGestureRecognizer
这个手势所处理的
- 一旦在某个页面自定义了系统导航栏的返回按钮,
interactivePopGestureRecognizer
就会让当前页面的左侧边缘返回手势失效, 但是其他使用系统返回按钮的页面依然可以右滑返回, 所以系统做了特殊处理
- 而这种情况是由
interactivePopGestureRecognizer
的 delegate
所导致的
- 解决方式是只要设置
interactivePopGestureRecognizer
的 delegate
为nil
, 如果自定义了系统导航栏的返回按钮, 当前页面也可以右滑返回
- 需要注意的是, 当
UINavigationController
只有一个子控制器时 , 这时如果在屏幕左侧边缘触发右滑手势, app 将会卡住无法响应, 这是因为 interactivePopGestureRecognizer
的 deleagete
此时不能为nil
, 需要delegate
来处理这种特殊的情况
- 自定义一个
UINavigationController
的子类, 在子类内部做特殊处理
代码如下:
#import "NavViewController.h"
@interface NavViewController ()
/** tz_PopDelegate */
@property (nonatomic, strong) id tz_PopDelegate;
@end
@implementation NavViewController
- (void)viewDidLoad {
[super viewDidLoad];
//保存 interactivePopGestureRecognizer 的delegate
self.tz_PopDelegate = self.interactivePopGestureRecognizer.delegate;
self.delegate = self;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
NSLog(@"控制器个数: %ld",self.childViewControllers.count);
self.interactivePopGestureRecognizer.enabled = YES;
if (viewController == self.viewControllers[0]) { //如果是在首页,设置interactivePopGestureRecognizer的delegate
self.interactivePopGestureRecognizer.delegate = self.tz_PopDelegate; // 不支持侧滑
} else { //如果是二级页面, 设置 interactivePopGestureRecognizer的代理为nil, 支持侧滑
self.interactivePopGestureRecognizer.delegate = nil; // 支持侧滑
}
}
@end
场景二 : 系统默认的侧滑返回手势, 只能在左侧边缘右滑触发, 而现在很多app 都有全屏返回这个需求
解决方法:
- 使用
Runtime+KVC
分析 UINavigationController
的 interactivePopGestureRecognizer
属性, 打印 interactivePopGestureRecognizer
这个对象
#import "NavViewController2.h"
@interface NavViewController2 ()
@end
@implementation NavViewController2
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"手势 : \n%@", self.interactivePopGestureRecognizer);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
手势 :
<
UIScreenEdgePanGestureRecognizer: 0x7f9def50ee30;
state = Possible;
delaysTouchesBegan = YES;
view = ;
target= <(
action=handleNavigationTransition:,
target=<_UINavigationInteractiveTransition 0x7f9def50c760>
)>
>
- 可以发现,
interactivePopGestureRecognizer
其实是UIScreenEdgePanGestureRecognizer
的一个实例
- 查看
UIScreenEdgePanGestureRecognizer
的头文件, 可以发现有一个属性
//
// UIScreenEdgePanGestureRecognizer.h
// Copyright (c) 2012-2017 Apple Inc. All rights reserved.
//
#import
#import
NS_ASSUME_NONNULL_BEGIN
/*! This subclass of UIPanGestureRecognizer only recognizes if the user slides their finger
in from the bezel on the specified edge. */
NS_CLASS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED @interface UIScreenEdgePanGestureRecognizer : UIPanGestureRecognizer
@property (readwrite, nonatomic, assign) UIRectEdge edges; //< The edges on which this gesture recognizes, relative to the current interface orientation
@end
NS_ASSUME_NONNULL_END
- 但是直接修改
edges
这个属性并不能扩大手势响应范围
- 上面的打印结果中, 有个属性是我们所需要的
target= <(
action=handleNavigationTransition:,
target=<_UINavigationInteractiveTransition 0x7f9def50c760>
)>
- 但是直接通过
KVC
获取这个属性程序会崩溃, 因为interactivePopGestureRecognizer
这个对象中并不存在target
这个属性
- 通过
Runtime
打印interactivePopGestureRecognizer
的所有属性, 结果如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"手势 : %@", self.interactivePopGestureRecognizer);
unsigned int count = 0;
Ivar *var = class_copyIvarList([UIGestureRecognizer class], &count);
for (int i = 0; i < count; i++) {
Ivar aVar = *(var + i);
NSLog(@"成员变量类型%d: %s", i,ivar_getTypeEncoding(aVar));
NSLog(@"成员变量名%d: %s",i,ivar_getName(aVar));
}
}
2018-04-17 10:58:53.095436+0800 ATest[1487:44665] 成员变量名0: _gestureFlags
2018-04-17 10:58:53.095597+0800 ATest[1487:44665] 成员变量类型1: @"NSMutableArray"
2018-04-17 10:58:53.095835+0800 ATest[1487:44665] 成员变量名1: _targets
2018-04-17 10:58:53.095989+0800 ATest[1487:44665] 成员变量类型2: @"NSMutableArray"
2018-04-17 10:58:53.096124+0800 ATest[1487:44665] 成员变量名2: _delayedTouches
2018-04-17 10:58:53.096228+0800 ATest[1487:44665] 成员变量类型3: @"NSMutableArray"
2018-04-17 10:58:53.096341+0800 ATest[1487:44665] 成员变量名3: _delayedPresses
2018-04-17 10:58:53.096466+0800 ATest[1487:44665] 成员变量类型4: @"UIView"
2018-04-17 10:58:53.096892+0800 ATest[1487:44665] 成员变量名4: _view
2018-04-17 10:58:53.097113+0800 ATest[1487:44665] 成员变量类型5: d
2018-04-17 10:58:53.097449+0800 ATest[1487:44665] 成员变量名5: _lastTouchTimestamp
2018-04-17 10:58:53.097763+0800 ATest[1487:44665] 成员变量类型6: d
2018-04-17 10:58:53.098047+0800 ATest[1487:44665] 成员变量名6: _firstEventTimestamp
2018-04-17 10:58:53.098333+0800 ATest[1487:44665] 成员变量类型7: q
2018-04-17 10:58:53.098614+0800 ATest[1487:44665] 成员变量名7: _state
2018-04-17 10:58:53.098872+0800 ATest[1487:44665] 成员变量类型8: q
2018-04-17 10:58:53.099156+0800 ATest[1487:44665] 成员变量名8: _allowedTouchTypes
2018-04-17 10:58:53.099388+0800 ATest[1487:44665] 成员变量类型9: q
2018-04-17 10:58:53.099604+0800 ATest[1487:44665] 成员变量名9: _initialTouchType
2018-04-17 10:58:53.099885+0800 ATest[1487:44665] 成员变量类型10: @"NSMutableSet"
2018-04-17 10:58:53.100095+0800 ATest[1487:44665] 成员变量名10: _internalActiveTouches
2018-04-17 10:58:53.100408+0800 ATest[1487:44665] 成员变量类型11: @"_UIForceLevelClassifier"
2018-04-17 10:58:53.100660+0800 ATest[1487:44665] 成员变量名11: _forceClassifier
2018-04-17 10:58:53.100925+0800 ATest[1487:44665] 成员变量类型12: q
2018-04-17 10:58:53.101146+0800 ATest[1487:44665] 成员变量名12: _requiredPreviewForceState
2018-04-17 10:58:53.101982+0800 ATest[1487:44665] 成员变量类型13: @"_UITouchForceObservable"
2018-04-17 10:58:53.102227+0800 ATest[1487:44665] 成员变量名13: _touchForceObservable
2018-04-17 10:58:53.102449+0800 ATest[1487:44665] 成员变量类型14: @"NSObservation"
2018-04-17 10:58:53.102747+0800 ATest[1487:44665] 成员变量名14: _touchForceObservableAndClassifierObservation
2018-04-17 10:58:53.103067+0800 ATest[1487:44665] 成员变量类型15: @"NSMutableArray"
2018-04-17 10:58:53.103271+0800 ATest[1487:44665] 成员变量名15: _forceTargets
2018-04-17 10:58:53.103465+0800 ATest[1487:44665] 成员变量类型16: Q
2018-04-17 10:58:53.103784+0800 ATest[1487:44665] 成员变量名16: _forcePressCount
2018-04-17 10:58:53.104055+0800 ATest[1487:44665] 成员变量类型17: @"NSObservationSource"
2018-04-17 10:58:53.104353+0800 ATest[1487:44665] 成员变量名17: _beganObservable
2018-04-17 10:58:53.104579+0800 ATest[1487:44665] 成员变量类型18: @"NSMutableSet"
2018-04-17 10:58:53.104839+0800 ATest[1487:44665] 成员变量名18: _failureRequirements
2018-04-17 10:58:53.105092+0800 ATest[1487:44665] 成员变量类型19: @"NSMutableSet"
2018-04-17 10:58:53.105312+0800 ATest[1487:44665] 成员变量名19: _failureDependents
2018-04-17 10:58:53.105589+0800 ATest[1487:44665] 成员变量类型20: @"NSMutableSet"
2018-04-17 10:58:53.105847+0800 ATest[1487:44665] 成员变量名20: _activeEvents
2018-04-17 10:58:53.106136+0800 ATest[1487:44665] 成员变量类型21: B
2018-04-17 10:58:53.106677+0800 ATest[1487:44665] 成员变量名21: _keepTouchesOnContinuation
2018-04-17 10:58:53.107423+0800 ATest[1487:44665] 成员变量类型22: @""
2018-04-17 10:58:53.107614+0800 ATest[1487:44665] 成员变量名22: _delegate
2018-04-17 10:58:53.107885+0800 ATest[1487:44665] 成员变量类型23: @"NSArray"
2018-04-17 10:58:53.108180+0800 ATest[1487:44665] 成员变量名23: _allowedPressTypes
2018-04-17 10:58:53.108481+0800 ATest[1487:44665] 成员变量类型24: @"NSString"
2018-04-17 10:58:53.108730+0800 ATest[1487:44665] 成员变量名24: _name
2018-04-17 10:58:53.109045+0800 ATest[1487:44665] 成员变量类型25: @"UIGestureEnvironment"
2018-04-17 10:58:53.109347+0800 ATest[1487:44665] 成员变量名25: _gestureEnvironment
- 可以发现,
interactivePopGestureRecognizer
这个对象中有_targets
这个可变数组, 这是我们想要的
- 打印
_targets
变量
//打印_tagets
NSMutableArray * _targets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
NSLog(@"_targets == %@",_targets);
NSLog(@"_targets[0] == %@",_targets[0]);
_targets == (
"(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7ffa6ec03560>)"
)
2018-04-17 11:13:33.695306+0800 ATest[1617:52185] _targets[0] == (action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7ffa6ec03560>)
- 可以发现,
_targets
这个数组中的元素是 target-action
这种格式的, 可以猜测系统的手势中, 是通过一个对象来保存手势的taget
和这个taget
对应的action
的, 并把这个对象添加到_targets
这个数组中保存起来, 通过断点调试得到保存target-action
的是UIGestureRecognizerTarget
这个私有类
- 最终解决方法如下:
@interface NavViewController2 ()
@property (nonatomic, weak) UIPanGestureRecognizer *popRecognizer;
@end
@implementation NavViewController2
- (void)viewDidLoad {
[super viewDidLoad];
// 注意 : 要让interactivePopGestureRecognizer失效
self.interactivePopGestureRecognizer.enabled = NO;
//自定义pan手势
UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] init];
popRecognizer.delegate = self;
popRecognizer.maximumNumberOfTouches = 1;
[self.interactivePopGestureRecognizer.view addGestureRecognizer:popRecognizer];
NSMutableArray *_targets = [self.interactivePopGestureRecognizer valueForKey:@"_targets"];
//获取保存target-action的对象, 是私有类 UIGestureRecognizerTarget 的实例
id gestureRecognizerTarget = [_targets firstObject];
//获取UIGestureRecognizerTarget中保存的target, 是私有类_UINavigationInteractiveTransition的实例
id interactiveTransition = [gestureRecognizerTarget valueForKey:@"_target"];
//获取UIGestureRecognizerTarget中保存的action, 是interactiveTransition中响应手势的方法
SEL handleTransition = NSSelectorFromString(@"handleNavigationTransition:");
//绑定taget-action
[popRecognizer addTarget:interactiveTransition action:handleTransition];
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
/**
* 这里有两个条件不允许手势执行:
* 1、当前控制器为根控制器;
* 2、如果这个push、pop动画正在执行(私有属性)
*/
return self.viewControllers.count != 1 && ![[self valueForKey:@"_isTransitioning"] boolValue];
}
@end
- 现在, 我们的app就支持丝滑的全屏返回手势了
- 还可以发现一个有趣的东西, 执行以下打印:
NSLog(@"\ninteractivePopGestureRecognizer.delegate:\n%@\ninteractiveTransition:\n%@",self.interactivePopGestureRecognizer.delegate, interactiveTransition);
NSLog(@"-----------------------------------------");
NSLog(@"\ninteractivePopGestureRecognizer.view:\n%@\nself.view:\n%@",self.interactivePopGestureRecognizer.view, self.view);
2018-04-17 11:59:32.095941+0800 ATest[2222:78230]
interactivePopGestureRecognizer.delegate:
<_UINavigationInteractiveTransition: 0x7fadf3404690>
interactiveTransition:
<_UINavigationInteractiveTransition: 0x7fadf3404690>
2018-04-17 11:59:32.096143+0800 ATest[2222:78230] -----------------------------------------
2018-04-17 11:59:32.096798+0800 ATest[2222:78230]
interactivePopGestureRecognizer.view:
; layer = >
self.view:
; layer = >
- 可以看到,
self.interactivePopGestureRecognizer.delegate
跟我们辛辛苦苦才获得的interactiveTransition
原来是同个对象, 这样我们可以直接使用self.interactivePopGestureRecognizer
的delegate
作为自定义手势的target
了
- 同样的,
self.interactivePopGestureRecognizer.view
其实就是UINavigationController
的view
, 所以可以对我们上面的viewDidLoad
代码做下简化
- (void)viewDidLoad {
[super viewDidLoad];
// 注意 : 要让interactivePopGestureRecognizer失效
self.interactivePopGestureRecognizer.enabled = NO;
//自定义pan手势
UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] init];
popRecognizer.delegate = self;
popRecognizer.maximumNumberOfTouches = 1;
[self.view addGestureRecognizer:popRecognizer];
//获取action
SEL handleTransition = NSSelectorFromString(@"handleNavigationTransition:");
//绑定taget-action
[popRecognizer addTarget:self.interactivePopGestureRecognizer.delegate action:handleTransition];
}
运行, 效果是一样的