事件产生
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,为什么是队列而不是栈?因为队列的特点是FIFO,即先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)。
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。
找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理。
事件的传递
触摸事件的传递是从父控件传递到子控件
也就是UIApplication->window->寻找处理事件最合适的view
注 意: 如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件
如何找到最合适的试图
通过调用 -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 返回可以处理事件的View 没有就字节处理
那么 -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 是如何实现的呢?
1.首先判断主窗口(keyWindow)自己是否能接受触摸事件
2.判断触摸点是否在自己身上-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 方法
3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
4.view,比如叫做fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的子控件,直至没有更合适的view为止。
5.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。
button上只有圆形可以处理事件
///> 返回可以处理事件的View
- (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 toView: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;
CGFloat y2 = self.frame.size.height / 2;
double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
// 67.923
if (dis <= self.frame.size.width / 2) {
return YES;
}
else{
return NO;
}
}
UIView不能接收触摸事件的三种情况:
1. 不允许交互:userInteractionEnabled = NO
2. 隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件
3. 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明。
事件的响应机制
viewA --> 一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃
如何做到多个view处理一个事件 重写
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 1.自己先处理事件...
NSLog(@"do somthing...");
// 2.再调用系统的默认做法,再把事件交给上一个响应者处理
[super touchesBegan:touches withEvent:event];
}
touchesBegan 方法
如果重写uibutton的 touchesBegan 方法但是没有写 super
就不会调用我们的点击方法
可以想象所有的事件调用都是从 touchesBegan 出去的。夫试图也会调用这个方法 来实现点击方法