1.事件的分发
在iOS中, 只有继承了UIResponder的类的对象才能接收并处理事件,我们称为响应者对象 UIApplication,UIViewController,UIView均为响应者对象, 都能够接收并处理事件。事件的分发,只会在他们之间进行。 默认的事件分发的过程 当用户点击了一个UIResponder: 1.系统会将此次触发事件加入到一个由UIApplication管理的队列事件中 2.UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常会先发送事件给应用程序的主窗口(keyWindow) 3.主窗口会判断能否处理事件,如果可以,开始在自己的根View上寻找处理事件的View 4.根View在自己的子View上找处理事件的子VIew(从最后一个添加的子View开始处理),如果找到,事件就交由他处理,响应链终止,如果没有就自己处理响应事件。可以处理事件的VIew的判断满足两点:该view能否处理事件;点击事件在VIew区域内。 5.反复步骤4,如果没有最适合处理事件的VIew就将事件交给KeyWindow处理
2.响应链传递相关函数
1.hitTest:withEvent:
只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法寻找处理事件的View,返回的就是处理事件的View
底层具体实现如下 :
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
CGPoint childPoint = [self convertPoint:point toView:childView];
UIView *hitView = [childView hitTest:childPoint withEvent:event];
if (hitView) {
return hitView;
}
}
return self;
}
事件传递给窗口或控件的后,就调用hitTest:withEvent:方法寻找更合适的view。所以是,先传递事件,再根据事件在自己身上找更合适的view。不管子控件是不是最合适的view,系统默认都要先把事件传递给子控件,经过子控件调用自己的hitTest:withEvent:方法验证后才知道有没有更合适的view。即便父控件是最合适的view了,子控件的hitTest:withEvent:方法还是会调用,不然怎么知道有没有更合适的!即,如果确定最终父控件是最合适的view,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的。 hitTest:withEvent:方法忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。 如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。
pointInside: withEvent:
该方法判断触摸点是否在控件身上, 是则返回YES, 否则返回NO. 当点击事件在view范围内,默认返回YES
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
3.相对位置转换函数
将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;
将像素point从view中转换到当前视图中,返回在当前视图中的像素值
- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;
将rect由rect所在视图转换到目标视图view中,返回在目标视图view中的rect
- (CGRect)convertRect:(CGRect)rect toView:(UIView *)view;
将rect从view中转换到当前视图中,返回在当前视图中的rect
- (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;
3.响应链使用举例
因为点击事件的传递,就是由控件能否处理事件和点击位置是否为处理位置决定,所以重写以上两个函数,可以实现: 点击视图1,由视图2来响应 让子控件位于父控件之外的部分,响应事件
1.点击视图1由视图2来响应
view1 和view2为同一父视图下不重叠的两个子视图
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
return _view2;
}
2.让子视图位于父视图之外的部分响应事件
重写父view 的hitTest: withEvent: 函数
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
for(int i=0;i<self.subviews.count;i++){
UIView *view = self.subviews[i];
CGPoint subPoint = [self convertPoint:point toView:view];
if([view pointInside:subPoint withEvent:event]){
return view;
}
}
return self;
}
4.其他
为什么手势和单击事件只会响应手势?
UIGestureRecognizer 有个属性cancelsTouchesInView,这个属性默认值是YES,即当手势识别成功后,会发送touchesCancelled消息给view来结束view的响应。 如果cancelsTouchesInView为NO,那么gestureRecognizer和view都可以响应
UIGestureRecognizer 有个属性cancelsTouchesInView,这个属性默认值是YES,即当手势识别成功后,会发送touchesCancelled消息给view来结束view的响应。
UIView不能接收触摸事件的三种情况
不接受用户交互:userInteractionEnabled = NO; 隐藏:hidden = YES; 透明:alpha = 0.0~0.01
|