理解
响应者链主要包含两个部分,一个是事件的传递,另外一个是响应事件
事件传递
事件传递.png- 触摸屏幕,产生事件,将事件传递到
UIApplication
管理的事件队列中 -
UIApplication
将事件传递给UIwindow -
UIWindow
调用hitTest: withEvent:
方法。-
hitTest: withEvent:
是UIView
的方法,UIWindow
是继承UIView
,所以也会调用这方法 -
hitTest: withEvent:
方法与pointInside: withEvent:
总是成对出现,hitTest: withEvent:
内部调用pointInside: withEvent:
方法,判断触摸点是否在当前视图内 - 若在当前视图内,就倒序遍历子视图,子视图也是重复上面几步,调用
hitTest: withEvent:
方法。如果hitTest: withEvent:
有返回非空值,那么hitTest: withEvent:
就返回这个子视图;如果UIWindow
所有子视图的hitTest: withEvent:
方法返回都为nil
,那么UIWindow
就是响应视图
-
响应事件
响应者链.png上面这个图是从苹果官方文档复制过来的,通过事件传递,找到适合的响应者成为
first responder
,即第一响应者
,如果第一响应者
未处理事件,就会根据响应者链,将事件传递给下一个响应者。UIView
→ UIView
→ UIViewController
(如果没有UIViewController
就直接传给UIWindow
) → UIWindow
→ UIApplicationDelegate
。若消息被处理了就默认不继续传递,若所有响应者都不处理,那就不处理了,啥也没发生。
DEMO
自定义响应范围的button写一个自定义UIButton
,点击button
圆圈范围内才响应事件,否则不响应。
.m
实现:
#import "MYButton.h"
@implementation MYButton
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 判断是否能响应事件
if ([self isHidden] ||
!self.userInteractionEnabled ||
self.alpha < 0.01) {
return nil;
}
// 不在点击范围
if (![self pointInside:point withEvent:event]) {
return nil;
}
// 倒序遍历
__block UIView *view = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 转换相对坐标
CGPoint convertP = [self convertPoint:point toView:obj];
view = [obj hitTest:convertP withEvent:event];
if (view) {
*stop = YES;
}
}];
if (view) {
return view;
}
else{
return self;
}
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
CGFloat x = point.x;
CGFloat y = point.y;
CGFloat r = self.bounds.size.width * 0.5;
CGFloat delta = sqrt((r-x) * (r-x) + (r-y) * (r-y));
if (delta <= r) {
return YES;
}
else{
return NO;
}
}
网友评论