iOS事件传递问题
今天在实际开发中遇到一些问题,就是需要一个按钮的点击事件区域是按钮的一部分区域,这就需要知道iOS的事件传递机制和事件点击区域判断问题,iOS中事件处理是通过下面两个方法实现的:
系统事件响应方法.png
那么从点击屏幕到事件响应并执行相应动作系统都做了什么呢如图:
事件响应流程.png
首先是我点击了屏幕,此时事件会传递给->UIApplication->UIWindeow->hitTest:withEvent:此时系统会做哪些事情呢:
系统hit事件流程.png
系统会判断一下当前控件是否能接受事件,当当前控件没有开启权限,透明度小于0.01或者是hidden状态的时候,将会返回nil标志当前控件并不是事件的接受者.会继续走下去,如果当前控件可以接受事件响应的话就需要判断点击的区域是否在控件的展示区域里,不在的话依旧返回nil.如果点击区域刚好在控件展示的区域内.呵呵没错这货就是承载这事件响应者的控件.但想要找到正在的响应者还需要把以上动作在subViews中遍历执行一遍知道在子视图中找到相应的控件返回(这里要明确一定系统在遍历subViews的时候是采用倒序形式).如果子视图中找不到,则把当前的控件返回.此时的控件就是不折不扣的事件响应者了.
说了那么多如果你还是不了解我们就以实际的按钮来说明一下.假设现在需要展示一个正方形的button,正方形的内切圆为按钮的点击事件.这就需要修改button的pointInside方法修改按钮的点击区域让相关区域事情事件传递的功能!
首先我们自定义一个按钮重写事件方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
//判断按钮是否可以接收事件响应
if (!self.userInteractionEnabled || [self isHidden] || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
//遍历当前对象的子视图
__block UIView *hit = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//坐标转化
CGPoint vonvertPoint = [self convertPoint:point fromView:obj];
//调用子视图的hittest方法
hit = [obj hitTest:vonvertPoint withEvent:event];
//如果找到了接受事件的对象,则停止遍历
if (hit) {
*stop = YES;
}
}];
if (hit) {
return hit;
}else{
return self;
}
}else{
return nil;
}
}
设置按钮的点击区域
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGFloat x1 = point.x;
CGFloat y1 = point.y;
CGFloat x2 = self.frame.size.width / 2.0;
CGFloat y2 = self.frame.size.height / 2.0;
double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2)*(y1 - y2));
//在以当前控件中心为圆心半径是当前控件的宽度的院内
if (dis < self.frame.size.width / 2.0) {
return YES;
}else{
NSLog(@"这里不是事件响应区域");
return NO;
}
}
完成button的内部代码实现我们看看效果,我们在ViewController创建我们自定义的button
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
testBtn = [[testButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
testBtn.backgroundColor = [UIColor redColor];
[testBtn addTarget:self action:@selector(doAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:testBtn];
}
//点击按钮触发事件响应
- (void)doAction:(testButton *)sender {
NSLog(@"click this button");
}
当我们点击按钮的中间内切圆的区域时
测试结果.png
这里打印了两次不是响应区域是因为我们把按钮的点击区域设置成了内切圆边缘区域不是事件响应者打印一次.返回了nil,之后事件会继续传递个他的父视图也就是View.由于事件响应者是按钮并不是View,所以View接受到事件传递的时候也会打印一次这样就如图所示打印了两次日志.至此整个事件传递的流程就完成了.
这里还要补充一点,如果事件没有找到响应者的话,系统会忽略这个事件当什么都没有发生,而不是抛出异常.
demo