背景
项目渐渐变大的过程中,由于ViewController 承担的业务逻辑越来越多,会遇到ViewController膨胀变得庞大无比的情况。比如在我做的项目中,有一个页面包含了展示信息业务、弹幕业务、其他附加业务,导致这个页面的ViewController超过了1000行,这么庞大的一个类,要修改东西第一个遇到的问题就是要查找定位,修改还极可能还会影响到其他地方,所以打算把这些模块独立出来,做成独立的ViewController,再在一个主要的ViewController中引入其他业务的ViewController。
遇到了一个问题是ViewController中的View事件透传的问题,加入了多个ViewController的View,这些View如果需要交互,就需要把View的userInteractionEnabled属性设置为YES,这样导致了事件会被顶部的View拦截,底下的View事件会接收不到。
结果
主要是使用了 hitTest: withEvent:
做了一个处理,对于当前View需要处理交互事件的子View,正常处理当前子View的事件,遇到空白的地方,则把事件继续传递到底下的View做处理,其他的页面也按照这种方式处理。
Demo: Demo
分析实现
hitTest: withEvent:
的作用就是返回一个可以处理用户事件的View,有以下几种情况
- 如果View是
UIControl
类型,则会调用对应的target - 如果是普通的View则会调用
beginTrackingWithTouch: withEvent:
等方法 - 在ViewController中则会调用
touchesBegan: withEvent
等方法。 - 如果
hitTest: withEvent:
返回一个nil则事件会传递到下一层做同样的处理。
下面是自定义的UIView, hitTest: withEvent:
处理需要交互的子View,如果点击的点位置不是需要加护的View的位置,返回nil,交个下一层处理。
@interface PTTransparentView() {}
@property (nonatomic, strong) NSMutableArray* hitViews;
@end
@implementation PTTransparentView
/**
把需要交互的View添加到当前View中
*/
- (void)addTouchableView:(UIView*)touchableView {
if (touchableView) {
[self.hitViews addObject:touchableView];
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
for (int i = 0; i<self.hitViews.count; i++) {
UIView* hitView = self.hitViews[i];
CGRect mappedFrame = [hitView convertRect:hitView.bounds toView:self];
if (CGRectContainsPoint(mappedFrame, point)) {
return hitView;
}
}
return nil;
}
- (NSMutableArray *)hitViews {
if (!_hitViews) {
_hitViews = [[NSMutableArray alloc] initWithCapacity:2];
}
return _hitViews;
}
@end
自定义的UIView在ViewController中的使用,
- 重写UIViewController的方法
loadView
初始化 self.view。 - 设置需要交互的子View
示例代码如下:
#import "AViewController.h"
#import "PTTransparentView.h"
@interface AViewController ()
@property (nonatomic, strong) UIButton* helpButton;
@end
@implementation AViewController
- (void)loadView {
self.view = [[PTTransparentView alloc] initWithFrame:[UIScreen mainScreen].bounds];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.userInteractionEnabled = YES;
[self.view addSubview:self.helpButton];
// 添加可点击的View
if ([self.view isKindOfClass:[PTTransparentView class]]) {
[((PTTransparentView*)self.view) addTouchableView:self.helpButton];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UIButton *)helpButton {
if (!_helpButton) {
_helpButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
_helpButton.frame = CGRectMake(100, 100, 100, 40);
[_helpButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[_helpButton setTitle:@"A帮助" forState:UIControlStateNormal];
[_helpButton addTarget:self action:@selector(onHelpBtnClick) forControlEvents:UIControlEventTouchUpInside];
}
return _helpButton;
}
- (void)onHelpBtnClick {
NSLog(@"====A帮助");
}
@end
在容器的ViewController添加了 AViewController 和 BViewController 对应的view,AViewController 和 BViewController 中各包含了一个可交互的按钮,点击按钮的位置处理按钮的点击事件,点击空白位置则不处理事件,事件会传递到下一层,如果点击的不是按钮的位置,最终事件会在容器的ViewController中的 touchesBegan: withEvent:
被处理。
@interface ViewController ()
@property (nonatomic, strong) AViewController* aVC;
@property (nonatomic, strong) BViewController* bVC;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.aVC.view];
self.aVC.view.frame = self.view.bounds;
[self.view addSubview:self.bVC.view];
self.bVC.view.frame = self.view.bounds;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (AViewController *)aVC {
if (!_aVC) {
_aVC = [AViewController new];
}
return _aVC;
}
- (BViewController *)bVC {
if (!_bVC) {
_bVC = [BViewController new];
}
return _bVC;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchesBegan====");
}
@end
总结
以上就是UIView事件传递处理的一个简单应用场景。