2019.5.29 修改Method Swizzling部分内容

优雅地开启全屏侧滑手势。


一、使用

作者给UINavigationController和UIViewController都添加了分类,并进行了默认的参数设置,因此不做任何配置就能拥有这个功能。

二、原理

作者通过方法交换,hook到系统原生push方法中的手势中的target和动画调用selector,创建自己的UIPanGestureRecognizer,并设置它的target和selector。

三、接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface UINavigationController (FDFullscreenPopGesture)
// 获取重新实现的侧滑返回手势对象
@property (nonatomic, strong, readonly) UIPanGestureRecognizer *fd_fullscreenPopGestureRecognizer;
// 是否允许视图控制器单独管理它对应的NavigationBar显示与隐藏.默认是YES
// 这个需要配合视图控制器的fd_prefersNavigationBarHidden属性来使用
// 也就是说如果把这个属性设置为NO, 视图控制器对应的导航栏的隐藏与否不由视图控制器决定
@property (nonatomic, assign) BOOL fd_viewControllerBasedNavigationBarAppearanceEnabled;
@end

@interface UIViewController (FDFullscreenPopGesture)
// 是否禁用全屏侧滑手势(默认NO)
@property (nonatomic, assign) BOOL fd_interactivePopDisabled;
// 表明当前控制器的导航栏是显示还是隐藏, 默认NO (显示导航栏)
@property (nonatomic, assign) BOOL fd_prefersNavigationBarHidden;
// 设置能够响应侧滑的最大边界(距离)
@property (nonatomic, assign) CGFloat fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
@end

四、源码阅读

_FDFullscreenPopGestureRecognizerDelegate

_FDFullscreenPopGestureRecognizerDelegate对象。遵循UIGestureRecognizerDelegate协议,主要用于决定控制器是否能响应手势。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@interface _FDFullscreenPopGestureRecognizerDelegate : NSObject <UIGestureRecognizerDelegate>
@property (nonatomic, weak) UINavigationController *navigationController;
@end
@implementation _FDFullscreenPopGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
// 栈的最顶层, 不需要响应手势
if (self.navigationController.viewControllers.count <= 1) {
return NO;
}

// 打开了禁用手势的开关
UIViewController *topViewController = self.navigationController.viewControllers.lastObject;
if (topViewController.fd_interactivePopDisabled) {
return NO;
}

// 超过自己设置的left edge
CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
CGFloat maxAllowedInitialDistance = topViewController.fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
return NO;
}

// 正在执行Transition动画
if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
return NO;
}

// 手机横屏状态下, 适配侧滑方向
// It tells you how far the touch moved since it was last reset. It resets when the touch goes down or if you reset it yourself.
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
BOOL isLeftToRight = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight;
CGFloat multiplier = isLeftToRight ? 1 : - 1; // 只能是从左向右滑
if ((translation.x * multiplier) <= 0) {
return NO;
}
return YES;
}
@end

UIViewController (FDFullscreenPopGesturePrivate)

UIViewController的分类。给其添加关联属性fd_willAppearInjectBlock。hook viewWillAppear方法并在其中调用fd_willAppearInjectBlock回调,hook viewWillDisappear方法,并在其中根据控制器的fd_prefersNavigationBarHidden属性来设置状态栏的显示与否。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);
@interface UIViewController (FDFullscreenPopGesturePrivate)
@property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;
@end
@implementation UIViewController (FDFullscreenPopGesturePrivate)
+ (void)load {
static dispatch_once_t onceToken;
// 方法交换, viewWillAppear和fd_viewWillAppear互换, viewWillDisappear和fd_viewWillDisappear互换
dispatch_once(&onceToken, ^{
Method viewWillAppear_originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
Method viewWillAppear_swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillAppear:));
method_exchangeImplementations(viewWillAppear_originalMethod, viewWillAppear_swizzledMethod);

Method viewWillDisappear_originalMethod = class_getInstanceMethod(self, @selector(viewWillDisappear:));
Method viewWillDisappear_swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillDisappear:));
method_exchangeImplementations(viewWillDisappear_originalMethod, viewWillDisappear_swizzledMethod);
});
}

- (void)fd_viewWillAppear:(BOOL)animated {
// 主类的实现 Forward to primary implementation.
[self fd_viewWillAppear:animated];
if (self.fd_willAppearInjectBlock) {
self.fd_willAppearInjectBlock(self, animated);
}
}

- (void)fd_viewWillDisappear:(BOOL)animated {
// Forward to primary implementation.
[self fd_viewWillDisappear:animated];
// 延迟为0相当于直接调用异步
// dispatch_async(dispatch_get_main_queue(), ^{});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UIViewController *viewController = self.navigationController.viewControllers.lastObject;
// viewController存在而且它的fd_prefersNavigationBarHidden为NO, 把NavigationBar显示出来
if (viewController && !viewController.fd_prefersNavigationBarHidden) {
[self.navigationController setNavigationBarHidden:NO animated:NO];
}
});
}

- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock {
return objc_getAssociatedObject(self, _cmd);
}

- (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block {
objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

UINavigationController (FDFullscreenPopGesture)

UINavigationController的分类。hookpushViewController:animated:方法,给响应push手势的view添加自定义的fd_fullscreenPopGestureRecognizer手势。当然,fd_fullscreenPopGestureRecognizer的target和selector与push原生手势的target及selector保持一致。除此之外,fd_fullscreenPopGestureRecognizer手势的代理是上面的_FDFullscreenPopGestureRecognizerDelegate对象,目的是决定是否响应手势。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@implementation UINavigationController (FDFullscreenPopGesture)
+ (void)load {
// Inject "-pushViewController:animated:"
// 交换pushViewController与fd_pushViewController
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(pushViewController:animated:);
SEL swizzledSelector = @selector(fd_pushViewController:animated:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

- (void)fd_pushViewController:(UIViewController *)viewController
animated:(BOOL)animated {
// 保证view只添加一次fd_fullscreenPopGestureRecognizer手势
if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {

// Add our own gesture recognizer to where the onboard screen edge pan gesture recognizer is attached to.
[self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];

// 获取私有变量target和selector
NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
// 设置滑动手势的代理
self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
[self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction];

// 禁用原生的手势
self.interactivePopGestureRecognizer.enabled = NO;
}

// 处理navigation bar 的显示与隐藏
[self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];

// 调用原来的方法实现(加一个判断, 避免重复push)
if (![self.viewControllers containsObject:viewController]) {
[self fd_pushViewController:viewController animated:animated];
}
}

- (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController {
// 如果fd_viewControllerBasedNavigationBarAppearanceEnabled设置为NO, 直接返回
if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) {
return;
}

// 配合fd_prefersNavigationBarHidden来使用
__weak typeof(self) weakSelf = self;
_FDViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
}
};

// 设置appearingViewController、disappearingViewController的fd_willAppearInjectBlock
appearingViewController.fd_willAppearInjectBlock = block;
UIViewController *disappearingViewController = self.viewControllers.lastObject;
if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
disappearingViewController.fd_willAppearInjectBlock = block;
}
}

- (_FDFullscreenPopGestureRecognizerDelegate *)fd_popGestureRecognizerDelegate {
_FDFullscreenPopGestureRecognizerDelegate *delegate = objc_getAssociatedObject(self, _cmd);
// 只初始化delegate一次
if (!delegate) {
delegate = [[_FDFullscreenPopGestureRecognizerDelegate alloc] init];
delegate.navigationController = self;

objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return delegate;
}

- (UIPanGestureRecognizer *)fd_fullscreenPopGestureRecognizer {
UIPanGestureRecognizer *panGestureRecognizer = objc_getAssociatedObject(self, _cmd);
// 只初始化panGestureRecognizer一次
if (!panGestureRecognizer) {
panGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
panGestureRecognizer.maximumNumberOfTouches = 1;
objc_setAssociatedObject(self, _cmd, panGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return panGestureRecognizer;
}

- (BOOL)fd_viewControllerBasedNavigationBarAppearanceEnabled {
NSNumber *number = objc_getAssociatedObject(self, _cmd);
if (number) { // BOOL值被封装成对象了
return number.boolValue;
}
// 走到这一步说明还没有设置过关联属性, 手动设置
self.fd_viewControllerBasedNavigationBarAppearanceEnabled = YES;
return YES;
}

- (void)setFd_viewControllerBasedNavigationBarAppearanceEnabled:(BOOL)enabled {
SEL key = @selector(fd_viewControllerBasedNavigationBarAppearanceEnabled);
objc_setAssociatedObject(self, key, @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

UIViewController (FDFullscreenPopGesture)

UIViewController的分类。给其添加关联属性fd_interactivePopDisabledfd_interactivePopMaxAllowedInitialDistanceToLeftEdgefd_prefersNavigationBarHidden

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@implementation UIViewController (FDFullscreenPopGesture)
- (BOOL)fd_interactivePopDisabled {
// 关于_cmd: 这行代码等价于 return [objc_getAssociatedObject(self, @selector(fd_interactivePopDisabled)) boolValue];
return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setFd_interactivePopDisabled:(BOOL)disabled {
objc_setAssociatedObject(self, @selector(fd_interactivePopDisabled), @(disabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)fd_prefersNavigationBarHidden {
return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setFd_prefersNavigationBarHidden:(BOOL)hidden {
// 存储的时候需要把基本数据类型包装成对象 @(hidden)
objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGFloat)fd_interactivePopMaxAllowedInitialDistanceToLeftEdge {
#if CGFLOAT_IS_DOUBLE // CGFLOAT_IS_DOUBLE宏: 64位下是1 否则0, 特别严谨
return [objc_getAssociatedObject(self, _cmd) doubleValue];
#else
return [objc_getAssociatedObject(self, _cmd) floatValue];
#endif
}

- (void)setFd_interactivePopMaxAllowedInitialDistanceToLeftEdge:(CGFloat)distance {
SEL key = @selector(fd_interactivePopMaxAllowedInitialDistanceToLeftEdge);
// 使用@(MAX(0, distance), 适配distance被外界设置为负值的情况
objc_setAssociatedObject(self, key, @(MAX(0, distance)), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

五、再谈Method Swizzling

实例方法交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@interface ViewController ()
@end
@implementation ViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"原始的方法实现");
}
@end

@implementation ViewController (MethodSwizzling1)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [self class];
SEL originalSel = @selector(viewWillAppear:);
SEL swizzledSel = @selector(ya1_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(cls, originalSel);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);
if (class_addMethod(cls, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(cls, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

- (void)ya1_viewWillAppear:(BOOL)animated {
[self ya1_viewWillAppear:animated];
NSLog(@"第一次在分类里面互换");
}
@end

类方法交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 交换类方法
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSel = @selector(a);
SEL swizzledSel = @selector(b);
Class class = object_getClass(self);
Method originalMethod = class_getInstanceMethod(class, originalSel);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSel);
if (class_addMethod(class, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(class, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

+ (void)b {
NSLog(@"b");
}

问题1:实例方法交换与类方法交换有什么区别?

没有大的变化,唯一的区别在于获取方法所属的对象上,一个获取的是类对象一个获取的是元类对象。即一个是[self class]或者说[self self],一个是object_getClass(self)(在类方法load中调用的)。
为什么会有这种区别?原因在于,实例方法存储在类对象中,类方法存储在元类对象中。

再一个,看看class的实现:

1
2
3
4
5
6
7
+ (Class)class {
return self;
}

- (Class)class {
return object_getClass(self);
}

网上许多文章这么说的:

object_getClass与self.class的区别
self.class:当self是实例对象的时候,返回的是类对象,否则则返回自身。
object_getClass:获得的是isa的指向

这个“返回自身”很含糊。

object_getClass()获取isa指向毋庸置疑。实例对象的isa指向类对象,类对象的isa指向元类对象,元类对象的isa指向根元类,根元类的isa指向它自己,耳熟能详。

关键是class,self指向了消息的接收者(“the object that’s received this message”),很自然地,实例方法的消息接收者是实例对象,类方法的消息接收者是类对象。根据代码实现来看,实例方法调用class是获取实例对象的isa指向,即类对象。而类对象调用class返回的消息接收者自己,这个“自身”指的就是类对象。于是,不管是实例对象还是类对象调用class方法,返回的总是类对象。

同样地,在实例方法的交换中,这几种获取类对象的方式是等价的:

1
2
3
Class class = [self self];
Class class = [self class];
Class class = self;

调用selfself方法也没啥奇怪的,源码是这样的:

1
2
3
4
5
6
7
- (id)self {
return self;
}

+ (Class)class {
return self;
}

问题2:能否做到实例方法与类方法交换?

实例方法与实例方法互换、类方法与类方法互换都很容易做到。那一个实例方法与一个类方法互换,或者一个类方法与一个实例方法互换可以做到吗?答案是肯定的。
根据上文讨论,关键在于获取方法所属的对象上,即巧妙控制好获取的类对象和元类对象即可。

(1)实例方法与类方法互换(新的实例方法交换原先的类方法)

1
2
3
4
SEL originalSel = @selector(classMethod);
SEL swizzledSel = @selector(newInstanceMethod);
Class originClass = object_getClass(self);
Class swizzleClass = self;

(2)类方法与实例方法互换(新的类方法交换原先的实例方法)

1
2
3
4
SEL originalSel = @selector(instanceMethod);
SEL swizzledSel = @selector(newClassMethod);
Class originClass = self;
Class swizzleClass = object_getClass(self);

二者的共同实现是这样的:

1
2
3
4
5
6
7
Method originalMethod = class_getInstanceMethod(originClass, originalSel);
Method swizzledMethod = class_getInstanceMethod(swizzleClass, swizzledSel);
if (class_addMethod(originClass, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(originClass, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}

问题3:方法二次交换是否会影响第一次交换(造成第一次交换失效)?

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

@implementation ViewController (MethodSwizzling2)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSel = @selector(viewWillAppear:);
SEL swizzledSel = @selector(ya2_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(self, originalSel);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSel);
if (class_addMethod(self, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(self, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

- (void)ya2_viewWillAppear:(BOOL)animated {
[self ya2_viewWillAppear:animated];
NSLog(@"第二次在分类里面互换");
}
@end

很明显不会。

原因: Compile Sources中设定了分类的编译顺序为ViewController+MethodSwizzling.m --> ViewController+MethodSwizzling2.mload方法的调用顺序也是这样。最新先调用主类的viewWillAppear:方法不必多说,接着调用分类ViewController+MethodSwizzling中的方法互换逻辑,使得第一次方法互换成功。紧接着调用分类ViewController+MethodSwizzling2中的方法互换逻辑,这个不会影响第一次方法互换的逻辑,相当于在第一次的方法互换之后再互换一次。

第一次: ya_viewWillAppear的实现与viewWillAppear的实现互换
第二次: ya2_viewWillAppear的实现与ya_viewWillAppear的实现互换,因为viewWillAppear的实现被ya_viewWillAppear代替了。所以主类和各个分类的方法都被清晰地调用了。

问题4:为什么需要class_replaceMethod()函数?

1,不单独使用method_exchangeImplementations()的原因?
原先的方法存在(有实现),自不必说,使用method_exchangeImplementations()直接交换函数指针即可。
但是如果原先的方法不存在,包括本来没有实现父类的方法。只使用method_exchangeImplementations(),会在本类没有实现父类的方法的情况下造成hook错误:自己的新方法跟父类的旧方法互换了,父类方法调用时一旦调用到子类特有的方法就会产生crash。

2,class_addMethod()与class_replaceMethod()配合的原因?
如果本类没有实现父类的方法,class_addMethod()会返回YES,此时旧selector、新selector都指向了新IMP,需要调用class_replaceMethod()把新selector指向旧IMP。

问题5:方法交换的标准姿势为什么是load方法配合dispatch_once?

常与+ (void)load;方法在一起比较的是+ (void)initialize;方法。
为什么是+ (void)load;方法?

  1. + (void)load;是在该类被加载到Runtime时调用的,在手动实现之后,一定会被调用,且正常情况下只会被调用一次,子类也不会多次调用父类的load方法(因为load方法时通过函数指针直接调用,而普通方法是通过消息机制调用。)。
  2. + (void)initialize;是在该类收到第一条消息前被调用,如果不向它发送消息(调用类方法或者实例方法),则该方法不会被调用。如果一个子类没有实现+ (void)initialize;方法,那么父类的该方法会被调用多次。

希望方法交换的逻辑一定会被执行,所以选择了load方法。最根本也最具有说服力的原因是initialize是基于消息机制的,如果在主类的initialize方法中实现了方法交换逻辑,在分类中又实现了initialize方法,由于分类中的方法在元类对象方法列表的前面,所以会造成方法交换逻辑并不会生效。而load方法是通过函数指针直接调用,父类、子类、父类的分类和子类的分类的load方法都会被依次调用,同样的情况,方法交换逻辑却依然会生效。再者,假定在父类的initialize方法中实现了方法交换逻辑,子类会调用父类的initialize方法,如果没有使用dispatch_once,会造成方法交换逻辑多次执行。而load方法,不需要也不应该调用[super load],总而言之就是,“initialize方法会造成方法交换不具备稳定性”。

为什么是dispatch_once
正常情况下load方法只会被执行一次,但是要考虑手动调用的情况(一般来说不需要手动调用):[ViewController load];。使用dispatch_once更加完备地保证只执行一次。

六、自定义侧滑手势

除了全屏侧滑之外,有些情况下需要自定义侧滑手势,这时可以使用UIScreenEdgePanGestureRecognizer实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)viewDidLoad {
[super viewDidLoad];
UIView *orangeView = [[UIView alloc] initWithFrame:self.view.bounds];
orangeView.backgroundColor = UIColor.orangeColor;
[self.view addSubview:orangeView];
UIScreenEdgePanGestureRecognizer *pan = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePop:)];
pan.edges = UIRectEdgeLeft;
[orangeView addGestureRecognizer:pan];
self.orangeView = orangeView;
}

- (void)handlePop:(UIScreenEdgePanGestureRecognizer *)pan {
void (^setOriginX)(UIView *, CGFloat) = ^(UIView *view, CGFloat x) {
[UIView animateWithDuration:0.15 animations:^{
CGRect frame = view.frame;
frame.origin.x = x;
view.frame = frame;
}];
};

UIView *targetView = pan.view;
CGFloat offsetX = [pan translationInView:targetView].x;
if (pan.state == UIGestureRecognizerStateChanged) {
targetView.center = CGPointMake(targetView.center.x + offsetX, targetView.center.y);
[pan setTranslation:CGPointZero inView:targetView.superview];
} else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
if (targetView.frame.origin.x / targetView.frame.size.width > 0.3) {
setOriginX(targetView, targetView.bounds.size.width);
} else {
setOriginX(targetView, 0);
}
}
}

七、总结

作为FDFullscreenPopGesture的源码阅读文章,实际上重心却不在它这。拜读下来,其实都是些常见的东西:关联属性,方法交换。其实解决问题最重要的,是思路。不然可能自己写了一大堆代码,也不能很好高效地解决问题。

以FDFullscreenPopGesture为引子,又重点回顾了方法交换,作为拓展,回答了引出的许多问题,像实例方法与类方法交换完全是脑洞来的,工作中我还没这么用过😂😂。最后简单介绍了UIScreenEdgePanGestureRecognizer,这个很好使的。

参考资料
一个丝滑的全屏滑动返回手势
iOS利用Runtime自定义控制器POP手势动画
iOS-FDFullscreenPopGesture详解