系统阶段
1、手指触碰屏幕,屏幕感应到后,将事件交收IOKit
处理.
2、IOKit
将事件封装成一个IOHIDEvent
对象,并通过mach port
传递给SpringBoard
进程。
3、SpringBoard进程接收到事件,将事件交给前台app
进程处理。
APP响应阶段
1、APP进行的mach_port接受到SpringBoard
传递来的事件,主线程的runloop
被唤醒,触发了source1回调。
2、source1触发了一个source0回调,将接收到的IOHIDEvent
对象封装成UIEvent
对象。
3、source0回调内部将触摸事件添加到UIApplication
对象的事件队列中。
4、事件出队后,UIApplication
调用当前UIWindow
对象的- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法,开始查找最佳响应者。
UIView 系统方法- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
大概逻辑实现
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
/**
官方文档描述:
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit. Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content.
*/
if ([self pointInside:point withEvent:event] && self.userInteractionEnabled && !self.hidden && self.alpha > 0.01) {
/**
因为view.subviews中,有可能多个view重叠放置,
视图最上层放在lastObject,最下层放在firstObject,
而事件响应者应该是上层的优先,所以倒序遍历子视图。
*/
NSArray *subViews = [[self.subviews reverseObjectEnumerator] allObjects];
UIView * tmpView = nil;
for (UIView *view in subViews) {
//将坐标转换为子视图为原点的坐标,其实就是坐标(x,y)减去子视图(x,y)
CGPoint convertedPoint = [self convertPoint:point toView:view];
//递归执行子视图的hitTest方法,直到找到最佳响应者并返回。
tmpView = [view hitTest:convertedPoint withEvent:event];
//如果找到view,其下层的view不再遍历,直接跳出循环。
if (tmpView) {
break;
}
}
//如果子视图中有符合条件的view,则返回子视图,如果没有,则返回self。
if (tmpView) {
return tmpView;
}
else
{
return self;
}
}
//不符合条件直接返回nil,如果父视图符合条件,则父视图作为事件响应者。如果没有事件响应者,则事件作废。
return nil;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
/**
判断点是否在当前bounds的范围内。
官方文档
YES if point is inside the receiver’s bounds; otherwise, NO.
Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clipsToBounds property is set to NO and the affected subview extends beyond the view’s bounds.
*/
return CGRectContainsPoint(self.bounds, point);
}
扩大点击区域
创建UIView或UIButton子类,重写- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
方法。
不建议重写- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect rect = [self enlargedRect];
if (CGRectEqualToRect(rect, self.bounds)) {
return [super hitTest:point withEvent:event];
}
return CGRectContainsPoint(rect, point);
}
//苹果推荐最少触摸范围为 44*44
- (CGRect)enlargedRect
{
CGRect rect = self.bounds;
if (rect.size.width < 44.0) {
rect.origin.x = rect.origin.x - (44.0 - rect.size.width) / 2;
rect.size.width = 44.0;
}
if (rect.size.height < 44.0) {
rect.origin.y = rect.origin.y - (44.0 - rect.size.height) / 2;
rect.size.height = 44.0;
}
return rect;
}
frame和区别bounds
frame是指在相对于父视图中,view的位置和大小。view在旋转之后,frame会改变,bouns不会。
bounds是指相对于自己子视图的原点和边界,默认为(0,0),修改origin影响子视图在view中的位置。
修改size,view的高和宽会以center为中心向四周扩散或收缩,会改变view的frame。
网友评论