hitTest:withEvent:方法
来源:互联网 发布:网络如何赚钱 编辑:程序博客网 时间:2024/06/04 19:50
一。 则hitTest:withEvent:方法调用过程
iOS系统检测到手指触摸(Touch)操作时会将其放入当前活动应用程序的事件队列,UIApplication会从事件队列中取出触摸事件并传递给关键窗口(当前接收用户事件的窗口)处理,窗口对象首先会使用hitTest:withEvent:方法寻找此次触摸操作初始点所在的视图(View),即需要触摸事件传递给其处理的视图,称之为hit-test视图。
窗口对象会在首先在视图层次结构的顶级视图上调用hitTest:withEvent:,此方法会在视图层级结构中的每个视图上调用点内部:withEvent:,如果pointInside :withEvent:返回YES,则继续逐级调用,直到找到触摸操作发生的位置,这个视图也就是测试视图。
则hitTest:withEvent:方法方法的处理流程如下:
- 首先调用当前视图的pointInside:withEvent:方法方法判断触摸点是否在当前视图内;
- 若返回NO,则则hitTest:withEvent:方法返回零;
- 若返回YES,则向当前视图的所有子视图(子视图)发送则hitTest:withEvent:方法消息,所有子视图的遍历顺序是从顶部到底部,即从子视图数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
- 若第一次有子视图返回非空对象,则则hitTest:withEvent:方法方法返回此对象,处理结束;
- 如所有子视图都返回非,则则hitTest:withEvent:方法方法返回自身(自)。
hitTest:withEvent:方法忽略隐藏(hidden = YES)的视图,禁止用户操作(userInteractionEnabled = YES)的视图,以及alpha级别小于0.01(alpha <0.01)的视图。如果一个子视图的区域超过父视图的约束区域(父视图的clipsToBounds 属性为NO,这样超过父视图绑定区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法。方法会返回NO,这样就不会继续向下遍历子视图了当然,也可以重写pointInside:withEvent:方法方法来处理这种情况。
对于每个触摸操作都会有一个UITouch对象,UITouch对象用来表示一个触摸操作,即一个手指在屏幕上按下,移动,离开的整个过程.UITouch对象在触摸操作的过程中在不断变化,所以在使用UITouch对象时,不能直接保留,而需要使用其他手段存储UITouch的内部信息.UITouch对象有一个视图属性,表示此触摸操作初始发生所在的视图,即上面检测到的命中测试视图,此属性在UITouch的生命周期不再改变,即使触摸操作后续移动到其他视图之上。
二定制。则hitTest:withEvent:方法方法
如果父视图需要对对哪个子视图可以响应触摸事件做特殊控制,则可以重写则hitTest:withEvent:方法或pointInside:withEvent:方法方法。
这里有几个例子:
- hitTest
在这个例子中的按钮,scrollview同为topView的子视图,但scrollview覆盖在按钮之上,这样在于按钮上的触摸操作返回的命中测试视图为scrollview,按钮无法响应,可以修改冠捷的则hitTest:withEvent:方法方法如下:- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *result = [super hitTest:point withEvent:event]; CGPoint buttonPoint = [underButton convertPoint:point fromView:self]; if ([underButton pointInside:buttonPoint withEvent:event]) { return underButton; } return result;}
这样如果触摸点在按钮的范围内,返回hittestView为按钮,从按钮按钮可以响应点击事件。
- 分页启用的UIScrollView与预览
BSPreviewScrollView
关于这两个例子,可以看之前文章的说明,见分页启用的UIScrollView
三。则hitTest:withEvent:方法探秘,诡异的三次调用
在测试中发现对每一次触摸操作实际会触发三次hitTest:withEvent:方法调用,有测试环境视图结构如下UIWindow-> UIScrollView-> TapDetectingImageView
- 首先在TapDetectingImageView的三次则hitTest:withEvent:方法两个打印查看参数有何不同
三次的打印查询查询结果分别为:point:{356.25, 232.031} event:<UITouchesEvent: 0x8653ea0> timestamp: 25269.8 touches: {()}point:{356.25, 232.031} event:<UITouchesEvent: 0x8653ea0> timestamp: 25269.8 touches: {()}point:{356.25, 232.031} event:<UITouchesEvent: 0x8653ea0> timestamp: 25272 touches: {()}
三次调用的点参数完全相同,事件从指针看为同一对象,但时间戳有变化,第一次和第二次的时间戳相同,第三次的与之前有区别。
- 深入检查事件参数
对于的UIEvent对象我们还可以查看其内部的数据,的UIEvent实际上是对GSEventRefs的包装,在GSEventRefs中又包含GSEventRecord,其结构如下:typedef struct __GSEvent { CFRuntimeBase _base; GSEventRecord record;} GSEvent; typedef struct __GSEvent* GSEventRef;typedef struct GSEventRecord { GSEventType type; // 0x8 //2 GSEventSubType subtype; // 0xC //3 CGPoint location; // 0x10 //4 CGPoint windowLocation; // 0x18 //6 int windowContextId; // 0x20 //8 uint64_t timestamp; // 0x24, from mach_absolute_time //9 GSWindowRef window; // 0x2C // GSEventFlags flags; // 0x30 //12 unsigned senderPID; // 0x34 //13 CFIndex infoSize; // 0x38 //14 } GSEventRecord;
在GSEventRecord中我们可以获取到GSEvent事件类型类型,windowLocation(在窗口坐标系中的位置)等参数。
访问的UIEvent中的GSEventRecord可以使用以下代码
if ([event respondsToSelector:@selector(_gsEvent)]) { #define GSEVENT_TYPE 2 #define GSEVENT_SUBTYPE 3 #define GSEVENT_X_OFFSET 6 #define GSEVENT_Y_OFFSET 7 #define GSEVENT_FLAGS 12 #define GSEVENTKEY_KEYCODE 15 #define GSEVENT_TYPE_KEYUP 11 int *eventMem; eventMem = (int *)objc_unretainedPointer([event performSelector:@selector(_gsEvent)]); if (eventMem) { int eventType = eventMem[GSEVENT_TYPE]; int eventSubType = eventMem[GSEVENT_SUBTYPE]; float xOffset = *((float*)(eventMem + GSEVENT_X_OFFSET)); float yOffset = *((float*)(eventMem + GSEVENT_Y_OFFSET)); }}
按照上文描述的方法我们获取到的UIEvent内部的windowLocation数据,同时将接收到的点参数在窗口坐标系中的位置也打印出,这样三次调用的数据分别为
point:{356.25, 232.031} windowPoint:{152, 232} event:<UITouchesEvent: 0x8653ea0> timestamp: 25269.8 touches: {()} gsEventType:3001 gsXoffset:213.000000 gsYoffset:316.000000point:{356.25, 232.031} windowPoint:{152, 232} event:<UITouchesEvent: 0x8653ea0> timestamp: 25269.8 touches: {()} gsEventType:3001 gsXoffset:213.000000 gsYoffset:316.000000point:{356.25, 232.031} windowPoint:{152, 232} event:<UITouchesEvent: 0x8653ea0> timestamp: 25272 touches: {()} gsEventType:3001 gsXoffset:152.000000 gsYoffset:232.000000
第一次和第二次调用的时间戳相同,GSEvent中的windowLocation也相同,但windowLocation并不是当前请求的点位置,第三次请求的时间戳与前两次不同,GSEvent中的windowLocation与当前请求的点位置一致。
多次点击可发现,第一次和第二次调用中的windowLocation数据实际上是上次点击操作的位置,猜测前两次则hitTest是对上次点击操作的终结?第三次则hitTest才是针对当前点击的。
- 栈调用分析
使用[NSThread callStackSymbols];
可以获取到当前线程的调用栈数据,在TapDetectingImageView的则hitTest:withEvent:方法中打印调用栈信息,分别为:
第一次调用:0 RenrenPhoto 0x0002b52d -[TapDetectingImageView hitTest:withEvent:] + 931 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 1322 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 3593 CoreFoundation 0x01b2903f __NSArrayEnumerate + 10234 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 1025 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 6406 UIKit 0x00497397 -[UIScrollView hitTest:withEvent:] + 797 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 1328 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 3599 CoreFoundation 0x01b2903f __NSArrayEnumerate + 102310 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 10211 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 64012 RenrenPhoto 0x0002e01b -[UIEventProbeWindow hitTest:withEvent:] + 39513 UIKit 0x00477a02 __47+[UIWindow _hitTestToPoint:pathIndex:forEvent:]_block_invoke_0 + 15014 UIKit 0x00477888 +[UIWindow _topVisibleWindowPassingTest:] + 19615 UIKit 0x00477965 +[UIWindow _hitTestToPoint:pathIndex:forEvent:] + 17716 UIKit 0x0043cd06 _UIApplicationHandleEvent + 169617 GraphicsServices 0x01ff8df9 _PurpleEventCallback + 33918 GraphicsServices 0x01ff8ad0 PurpleEventCallback + 4619 CoreFoundation 0x01aa4bf5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 5320 CoreFoundation 0x01aa4962 __CFRunLoopDoSource1 + 14621 CoreFoundation 0x01ad5bb6 __CFRunLoopRun + 211822 CoreFoundation 0x01ad4f44 CFRunLoopRunSpecific + 27623 CoreFoundation 0x01ad4e1b CFRunLoopRunInMode + 12324 GraphicsServices 0x01ff77e3 GSEventRunModal + 8825 GraphicsServices 0x01ff7668 GSEventRun + 10426 UIKit 0x0043c65c UIApplicationMain + 121127 RenrenPhoto 0x000026b2 main + 17828 RenrenPhoto 0x000025b5 start + 5329 ??? 0x00000001 0x0 + 1
第二次调用
0 RenrenPhoto 0x0002b52d -[TapDetectingImageView hitTest:withEvent:] + 931 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 1322 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 3593 CoreFoundation 0x01b2903f __NSArrayEnumerate + 10234 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 1025 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 6406 UIKit 0x00497397 -[UIScrollView hitTest:withEvent:] + 797 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 1328 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 3599 CoreFoundation 0x01b2903f __NSArrayEnumerate + 102310 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 10211 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 64012 RenrenPhoto 0x0002e01b -[UIEventProbeWindow hitTest:withEvent:] + 39513 UIKit 0x00477a02 __47+[UIWindow _hitTestToPoint:pathIndex:forEvent:]_block_invoke_0 + 15014 UIKit 0x00477888 +[UIWindow _topVisibleWindowPassingTest:] + 19615 UIKit 0x00477965 +[UIWindow _hitTestToPoint:pathIndex:forEvent:] + 17716 UIKit 0x0043cfd3 _UIApplicationHandleEvent + 241317 GraphicsServices 0x01ff8df9 _PurpleEventCallback + 33918 GraphicsServices 0x01ff8ad0 PurpleEventCallback + 4619 CoreFoundation 0x01aa4bf5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 5320 CoreFoundation 0x01aa4962 __CFRunLoopDoSource1 + 14621 CoreFoundation 0x01ad5bb6 __CFRunLoopRun + 211822 CoreFoundation 0x01ad4f44 CFRunLoopRunSpecific + 27623 CoreFoundation 0x01ad4e1b CFRunLoopRunInMode + 12324 GraphicsServices 0x01ff77e3 GSEventRunModal + 8825 GraphicsServices 0x01ff7668 GSEventRun + 10426 UIKit 0x0043c65c UIApplicationMain + 121127 RenrenPhoto 0x000026b2 main + 17828 RenrenPhoto 0x000025b5 start + 5329 ??? 0x00000001 0x0 + 1
第三次调用:
0 RenrenPhoto 0x0002b52d -[TapDetectingImageView hitTest:withEvent:] + 931 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 1322 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 3593 CoreFoundation 0x01b2903f __NSArrayEnumerate + 10234 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 1025 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 6406 UIKit 0x00497397 -[UIScrollView hitTest:withEvent:] + 797 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 1328 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 3599 CoreFoundation 0x01b2903f __NSArrayEnumerate + 102310 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 10211 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 64012 RenrenPhoto 0x0002e01b -[UIEventProbeWindow hitTest:withEvent:] + 39513 UIKit 0x0043d986 _UIApplicationHandleEvent + 489614 GraphicsServices 0x01ff8df9 _PurpleEventCallback + 33915 GraphicsServices 0x01ff8ad0 PurpleEventCallback + 4616 CoreFoundation 0x01aa4bf5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 5317 CoreFoundation 0x01aa4962 __CFRunLoopDoSource1 + 14618 CoreFoundation 0x01ad5bb6 __CFRunLoopRun + 211819 CoreFoundation 0x01ad4f44 CFRunLoopRunSpecific + 27620 CoreFoundation 0x01ad4e1b CFRunLoopRunInMode + 12321 GraphicsServices 0x01ff77e3 GSEventRunModal + 8822 GraphicsServices 0x01ff7668 GSEventRun + 10423 UIKit 0x0043c65c UIApplicationMain + 121124 RenrenPhoto 0x000026b2 main + 17825 RenrenPhoto 0x000025b5 start + 5326 ??? 0x00000001 0x0 + 1
从调用栈上看,三次调用的路径都不相同,关键区分点在_UIApplicationHandleEvent函数中,
第一次的调用位置为_UIApplicationHandleEvent + 1696
第二次的调用位置为_UIApplicationHandleEvent + 2413
第三次的调用位置为_UIApplicationHandleEvent + 4896 - 结论
没有结论,具体机制仍然是扑朔迷离,搞不懂...。,实际写代码时也不需要考虑这些。
参考:
iOS事件处理指南 - iOS
事件处理指南 - 命中测试
UIView类参考
iOS的事件处理 - hitTest:withEvent:和pointInside:withEvent:相关?
UITouch类参考
hitTest 攻击响应者链
使用预览启用Paging启用的UIScrollView
BSPreviewScrollView
启用分页功能的UIScrollView
在iOS中捕获键盘事件
Kenny TM GoogleCode Repo - GSEvent.h(有点旧但仍然有用)
Kenny TM Github Repo - GSEvent.h
拦截状态在iPhone上触摸iPhone
合成触摸事件
本文出自清风徐来,水波不兴的博客,转载时请注明出处及相应链接。
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法流程
- hitTest:withEvent:方法
- UIView hitTest:withEvent:方法流程
- [转]hitTest:withEvent:方法…
- hitTest:withEvent:
- hitTest:withEvent:
- hitTest:withEvent:
- 小胖说事45-----iOS hitTest:withEvent:方法流程
- hitTest:withEvent:和pointInside:withEvent:
- iOS hitTest:withEvent:
- hitTest:withEvent:调用过程
- androidx86编译踩坑
- cuda矩阵编程(一)
- hbase中创建表、插入数据,更新数据,删除数据
- poj1797
- Redis探索之旅(1)- Redis初识
- hitTest:withEvent:方法
- quartz定时任务时间设置
- Springmvc视图和视图解析器(四)
- Hbase之插入数据
- Android中常用的选择图像,跟换图像等(图像放大缩小等)
- OpenJ_POJ
- iOS UITextField输入手机号时自动添加空格
- 图片加载显示在listview上
- 编程点滴