关于 如何在视图内获取父视图的视图控制器
网上有很多类似主题的文章, 但是方法比较麻烦, 并且扩展性比较差. 于是自己总结一个易于扩展的方法.
UIResponder
UIResponder
主要职责是 iOS 中专门用来响应用户操作. 我们常用的 UI 相关的类有很大一部分都继承自它.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NSObject
|- UIResponder
|- UIApplication
|- UIView
| |- UIWebView
| |- UIControl
| |- UIButton
| |- UIDatePicker
| |- UIPageControl
| |- UISegmentControl
| |- UITextField
| |- UISlider
| |- UISwitch
|- UIViewController
|- UISplitViewController
|- UITabBarController
|- UITableViewController
|- UINavigationViewController
|- UIImagePickerViewController
|- UIVideoEditorController
继承链上的每一个类都是我们非常熟悉的.
关于 响应链
此处不做过多讲解, 后面另开新坑详细说.
UIResponder
有这样一个属性:
1
2
3
// Returns the next responder in the responder chain, or nil if there is no next responder.
// 返回当前对象在响应链中的下一响应对象
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
在 UI 层级中我们的 UI 层级之间究竟有什么样的关系? 首先创建如下 UI 作为测试工程:
UI 层级关系是:
1
2
3
4
5
UITabBarController(Root)
|- UINavigationController
|- UIViewController
|- UIViewController.view
|- CustomView
我们可以通过下面一段代码来验证(CustomView):
1
2
3
4
5
6
7
8
9
10
11
@implementation CustomView
- (void)didMoveToWindow {
UIResponder *responder = self;
while (responder != nil) {
NSLog(@"%@", [responder class]);
responder = [responder nextResponder];
}
}
@end
打印结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CustomView
UIView
ViewController
UIViewControllerWrapperView
UINavigationTransitionView
UILayoutContainerView
UINavigationController
UIViewControllerWrapperView
UITransitionView
UILayoutContainerView
UITabBarController
UIWindow
UIApplication
AppDelegate
以上就是图片中 UI 层级中 响应链
中的对象由上往下类的列表.
获取响应链层级中的 UIViewController
回归正题, 本文主要通过响应链来获取 UI
层级中某层对象.
就如 CustomView
中实现的代码部分一样我们可以通过循环遍历 [responder nextResponder]
来获取某一层级中的对象. 但是这种方法泛用性太差, 我们来找一个泛用性更高的方法.
正如上文所述, 我们常用的 UI 控件/对象
一大部分都继承自 UIResponder
, 并且 nextResponder
属性也继承自 UIResponder
, 我们就通过给 UIResponder
添加 Category
的方法来实现我们的目的.
创建 Category
获取 CustomView
父视图的视图控制器:
1
2
3
4
5
6
7
8
9
10
@implementation UIResponder (Category)
- (UIViewController *)nextViewController {
if ([self isKindOfClass:[UIViewController class]]) {
return (UIViewController *)self;
}
return [[self nextResponder] nextViewController];
}
@end
这样就达成了我们标题的目的, 但是泛用性方面太差. 我们对方法进行修改:
1
2
3
4
5
6
7
8
9
10
11
12
/**
获取响应链中指定类的实例对象
@param class 筛选类
@return 实例对象
*/
- (UIResponder *)nextResponder:(Class)class {
if ([self isKindOfClass:class]) {
return self;
}
return [self nextResponder:class];
}
当然, 这个方法也有其缺陷, 比如: 如果我们的 响应链
层级中有 n
个 CustomViewController
的实例对象, 我们就只能获取到最高层的那个, 而无法获取更加底层的实例对象.
当然, 你也可以通过 做标记
等多种方法来实现, 自由度非常高, 完全由你自己把控.