标签:style blog http color 使用 os strong 数据
在iOS 中,一个 UIResponder 对象表示一个可以接收触摸屏上的触摸事件的对象,通俗一点的说,就是表示一个可以接收事件的对象。
iOS 中,所有显示在界面上的对象都是从 UIResponder 直接或间接继承的。
下面是 UIResponder 类的一些定义信息:
触摸事件相关:
方法名称 |
说明 |
touchesBegan:withEvent |
当用户触摸到屏幕时调用方法 |
tochesMoved:withEvent |
当用户触摸到屏幕并移动时调用此方法 |
tochesEnded:withEvent |
当触摸离开屏幕时调用此方法 |
tochesCancelled:withEvent |
当触摸被取消时调用此方法 |
运动事件相关:
运动事件是指当用户以特定方式移动设置,如摇摆设置时,设置会产生运动事件,由以下几个方法进行处理:
方法名称 |
说明 |
motionBegan:withEvent |
运动开始时执行 |
motionEnded:withEvent |
运动结束时执行 |
motionCancelled:withEvent |
运动被取消时执行 |
远程事件相关:
方法名称 |
说明 |
remoteControlReceivedWithEvent |
接收到一个远程控制事件。比如耳机控制。
允许传递远程控制事件,必须调用UIApplication的beginReceivingRemoteControlEvents方法;关闭远程控制,调用endReceivingRemoteControlEvents。
|
响应对象链相关:
方法名称 |
说明 |
isFirstResponder |
指示对象是否为第一响应者,这里的第一响应者就是当前有焦点的对象, |
nextResponder |
下一个响应者,在实现中,一般会返回父级对象 UIResponder类不自动存储和设置下一个响应者,而是默认返回nil。子类必须override这个方法来设置下一个响应者。 UIView实现了这个方法,因为可以返回管理这个UIView的UIViewController或者它的父类; UIViewController实现了这个方法,返回UIViewController的View的父View; UIWindow发挥UIApplication对象; UIApplication返回nil |
canBecomeFirstResponder |
判断一个对象是否可以成为第一响应者。默认返回NO。
如果一个响应对象通过这个方法返回YES,那么它成为了第一响应对象,并且可以接收触摸事件和动作消息。 子类必须overrider这个方法才可以成为第一响应者。
|
becomeFirstResponder |
把对象设置为 firstResponder 对象默认返回YES。 |
canResignFirstResponder |
对象是否可以取消 firstResponder 对象 默认返回YES。 |
resignFirstResponder |
取消对象为 firstResponder 对象 |
在一个应用程序中,首先接收各种事件的响应者对象被称为第一响应者。它接收键盘事件、运动事件和动作消息,等等.
要当第一响应对象,还需要有View来毛遂自荐:
- (BOOL) canBecomeFirstResponder
{
returnYES;
}
如果缺少了这段,就算用[view becomeFirstResponder]也不能让一个view成为第一响应
对象。。。强扭的瓜不甜?好吧不是这个原因。大多数视图默认只关心与自己有关联的
事件,并且(几乎)总是有机会来处理这些事件。以UIButton为例,当用户单击某个UIB
utton对象时,无论当前的第一响应对象是哪个视图,该对象都会收到指定的动作消息。
当上第一响应对象吃力不讨好么。。。所以只能由某个UIResponder明确表示自己愿意成
为第一响应对象才行。(我不知道设计上是基于什么考虑。。。安全?)
在当上第一响应对象时,不同对象可能会有一些特殊的表现。例如UITextField当上的时
候,就会调出一块小键盘。
第一响应对象也有可能被辞退。发送一个resignFirstResponder,就可以劝退。
extension UIView{ func findFirstResponder()->UIView? { if self.isFirstResponder() { return self; } for subView in self.subviews as [UIView] { let firstResponder = subView.findFirstResponder(); if firstResponder{ return subView; } } return nil; } } class TestView : UIView{ let name:String; init(name:String,frame: CGRect) { self.name = name; super.init(frame: frame); } override func canBecomeFirstResponder() -> Bool { return true; } override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) { var responder = UIApplication.sharedApplication().delegate.window?.findFirstResponder() println("responder:\(responder)|is:\(self.isFirstResponder())|name:\(self.name)") } } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var label = UILabel(frame: CGRect(x:0,y:UIScreen.mainScreen().bounds.height/2+50,width:320,height:44)); label.text = "aaaaa"; self.view.addSubview(label); var testView1 = TestView(name: "view1",frame:CGRect(x: 0.0, y: 0.0, width: 300.0, height: 300.0)); testView1.backgroundColor = UIColor.yellowColor(); var testView2 = TestView(name:"view2",frame:CGRect(x: 0, y: 0, width: 200 , height: 200)); testView2.backgroundColor = UIColor.redColor(); testView1.becomeFirstResponder(); testView1.addSubview(testView2); self.view.addSubview(testView1); // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
输入视图是指当对象为 firstResponder 对象时,显示另外一个视图用来处理当前对象的信息输入,如 UITextView 和 UITextField 两个对象,在 UITextField 成为firstResponder 对象时,会显示一个系统键盘,用来输入信息。这个键盘视图就是一个输入视图了。一共有两个相关的输入视图,一个是 inputView, 另一个是inputAccessoryView,这两个视图显示的关系如下图:
从图中可以看到, 如果 inputView 和 inputAccessoryView 两个属性都指定了相应的视图,则 inputAccessoryView 对象显示在 inputView 对象的上面。
与输入相关的还有一个 reloadInputViews 方法用来重新载入输入视图。
在IOS中通常使用hit-testing去找到那个被触摸的视图。这个视图叫hit-test view,当IOS找到hit-test view后就把touch event交个那个视图来处理。下面画个图来说明一下,当点击视图E时看一下hit-testing的工作过程。
1.确定改触摸事件发生在view A范围内,接下来测试view B以及view C。
2.检查发现事件不再view B范围内发生,接下来检查view C发现触摸事件发生在了view C中,所以检查 view D,view E。
3.最后发现事件发生在view E的范围内所以view E成为了hit-test view。
下面是关于调用hit-test的官方说明:
The hitTest:withEvent:
method returns the hit test view for a given CGPoint
and UIEvent
. The hitTest:withEvent:
method begins by calling thepointInside:withEvent:
method on itself. If the point passed into hitTest:withEvent:
is inside the bounds of the view, pointInside:withEvent:
returns YES
. Then, the method recursively calls hitTest:withEvent:
on every subview that returns YES
.
iOS系统检测到手指触摸(Touch)操作时会将其放入当前活动Application的事件队列,UIApplication会从事件队列中取出触摸事件并传递给key window(当前接收用户事件的窗口)处理,window对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,称之为hit-test view。
window对象会在首先在view hierarchy的顶级view上调用hitTest:withEvent:,此方法会在视图层级结构中的每个视图上调用pointInside:withEvent:,如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是hit-test view。
hitTest:withEvent:方法的处理流程如下:
hitTest:withEvent:方法忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。
对于每个触摸操作都会有一个UITouch对象,UITouch对象用来表示一个触摸操作,即一个手指在屏幕上按下、移动、离开的整个过程。UITouch对象在触摸操作的过程中在不断变化,所以在使用UITouch对象时,不能直接retain,而需要使用其他手段存储UITouch的内部信息。UITouch对象有一个view属性,表示此触摸操作初始发生所在的视图,即上面检测到的hit-test view,此属性在UITouch的生命周期不再改变,即使触摸操作后续移动到其他视图之上。
如果父视图需要对对哪个子视图可以响应触摸事件做特殊控制,则可以重写hitTest:withEvent:或pointInside: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;
}
这样如果触摸点在button的范围内,返回hittestView为button,从button按钮可以响应点击事件。
在测试中发现对每一次触摸操作实际会触发三次hitTest:withEvent:方法调用,有测试环境视图结构如下 UIWindow->UIScrollView->TapDetectingImageView
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: {()}
三次调用的point参数完全相同,event从指针看为同一对象,但时间戳有变化,第一次和第二次的时间戳相同,第三次的与之前有区别。
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事件类型type,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参数在window坐标系中的位置也打印出,这样三次调用的数据分别为
point:{356.25, 232.031} windowPoint:{152, 232} event:<UITouchesEvent: 0x8653ea0> timestamp: 25269.8 touches: {()} gsEventType:3001 gsXoffset:213.000000 gsYoffset:316.000000
point:{356.25, 232.031} windowPoint:{152, 232} event:<UITouchesEvent: 0x8653ea0> timestamp: 25269.8 touches: {()} gsEventType:3001 gsXoffset:213.000000 gsYoffset:316.000000
point:{356.25, 232.031} windowPoint:{152, 232} event:<UITouchesEvent: 0x8653ea0> timestamp: 25272 touches: {()} gsEventType:3001 gsXoffset:152.000000 gsYoffset:232.000000
第一次和第二次调用的时间戳相同,GSEvent中的windowLocation也相同,但windowLocation并不是当前请求的point位置,第三次请求的时间戳与前两次不同,GSEvent中的windowLocation与当前请求的point位置一致。
多次点击可发现,第一次和第二次调用中的windowLocation数据实际上是上次点击操作的位置,猜测前两次hitTest是对上次点击操作的终结?第三次hitTest才是针对当前点击的。
[NSThread callStackSymbols];
可以获取到当前线程的调用栈数据,在TapDetectingImageView的hitTest:withEvent:中打印调用栈信息,分别为:
第一次调用:
0 RenrenPhoto 0x0002b52d -[TapDetectingImageView hitTest:withEvent:] + 93
1 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 132
2 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 359
3 CoreFoundation 0x01b2903f __NSArrayEnumerate + 1023
4 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 102
5 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 640
6 UIKit 0x00497397 -[UIScrollView hitTest:withEvent:] + 79
7 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 132
8 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 359
9 CoreFoundation 0x01b2903f __NSArrayEnumerate + 1023
10 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 102
11 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 640
12 RenrenPhoto 0x0002e01b -[UIEventProbeWindow hitTest:withEvent:] + 395
13 UIKit 0x00477a02 __47+[UIWindow _hitTestToPoint:pathIndex:forEvent:]_block_invoke_0 + 150
14 UIKit 0x00477888 +[UIWindow _topVisibleWindowPassingTest:] + 196
15 UIKit 0x00477965 +[UIWindow _hitTestToPoint:pathIndex:forEvent:] + 177
16 UIKit 0x0043cd06 _UIApplicationHandleEvent + 1696
17 GraphicsServices 0x01ff8df9 _PurpleEventCallback + 339
18 GraphicsServices 0x01ff8ad0 PurpleEventCallback + 46
19 CoreFoundation 0x01aa4bf5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
20 CoreFoundation 0x01aa4962 __CFRunLoopDoSource1 + 146
21 CoreFoundation 0x01ad5bb6 __CFRunLoopRun + 2118
22 CoreFoundation 0x01ad4f44 CFRunLoopRunSpecific + 276
23 CoreFoundation 0x01ad4e1b CFRunLoopRunInMode + 123
24 GraphicsServices 0x01ff77e3 GSEventRunModal + 88
25 GraphicsServices 0x01ff7668 GSEventRun + 104
26 UIKit 0x0043c65c UIApplicationMain + 1211
27 RenrenPhoto 0x000026b2 main + 178
28 RenrenPhoto 0x000025b5 start + 53
29 ??? 0x00000001 0x0 + 1
第二次调用
0 RenrenPhoto 0x0002b52d -[TapDetectingImageView hitTest:withEvent:] + 93
1 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 132
2 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 359
3 CoreFoundation 0x01b2903f __NSArrayEnumerate + 1023
4 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 102
5 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 640
6 UIKit 0x00497397 -[UIScrollView hitTest:withEvent:] + 79
7 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 132
8 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 359
9 CoreFoundation 0x01b2903f __NSArrayEnumerate + 1023
10 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 102
11 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 640
12 RenrenPhoto 0x0002e01b -[UIEventProbeWindow hitTest:withEvent:] + 395
13 UIKit 0x00477a02 __47+[UIWindow _hitTestToPoint:pathIndex:forEvent:]_block_invoke_0 + 150
14 UIKit 0x00477888 +[UIWindow _topVisibleWindowPassingTest:] + 196
15 UIKit 0x00477965 +[UIWindow _hitTestToPoint:pathIndex:forEvent:] + 177
16 UIKit 0x0043cfd3 _UIApplicationHandleEvent + 2413
17 GraphicsServices 0x01ff8df9 _PurpleEventCallback + 339
18 GraphicsServices 0x01ff8ad0 PurpleEventCallback + 46
19 CoreFoundation 0x01aa4bf5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
20 CoreFoundation 0x01aa4962 __CFRunLoopDoSource1 + 146
21 CoreFoundation 0x01ad5bb6 __CFRunLoopRun + 2118
22 CoreFoundation 0x01ad4f44 CFRunLoopRunSpecific + 276
23 CoreFoundation 0x01ad4e1b CFRunLoopRunInMode + 123
24 GraphicsServices 0x01ff77e3 GSEventRunModal + 88
25 GraphicsServices 0x01ff7668 GSEventRun + 104
26 UIKit 0x0043c65c UIApplicationMain + 1211
27 RenrenPhoto 0x000026b2 main + 178
28 RenrenPhoto 0x000025b5 start + 53
29 ??? 0x00000001 0x0 + 1
第三次调用:
0 RenrenPhoto 0x0002b52d -[TapDetectingImageView hitTest:withEvent:] + 93
1 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 132
2 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 359
3 CoreFoundation 0x01b2903f __NSArrayEnumerate + 1023
4 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 102
5 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 640
6 UIKit 0x00497397 -[UIScrollView hitTest:withEvent:] + 79
7 UIKit 0x0047f4d5 __38-[UIView(Geometry) hitTest:withEvent:]_block_invoke_0 + 132
8 CoreFoundation 0x01b515a7 __NSArrayChunkIterate + 359
9 CoreFoundation 0x01b2903f __NSArrayEnumerate + 1023
10 CoreFoundation 0x01b28a16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 102
11 UIKit 0x0047f3d1 -[UIView(Geometry) hitTest:withEvent:] + 640
12 RenrenPhoto 0x0002e01b -[UIEventProbeWindow hitTest:withEvent:] + 395
13 UIKit 0x0043d986 _UIApplicationHandleEvent + 4896
14 GraphicsServices 0x01ff8df9 _PurpleEventCallback + 339
15 GraphicsServices 0x01ff8ad0 PurpleEventCallback + 46
16 CoreFoundation 0x01aa4bf5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
17 CoreFoundation 0x01aa4962 __CFRunLoopDoSource1 + 146
18 CoreFoundation 0x01ad5bb6 __CFRunLoopRun + 2118
19 CoreFoundation 0x01ad4f44 CFRunLoopRunSpecific + 276
20 CoreFoundation 0x01ad4e1b CFRunLoopRunInMode + 123
21 GraphicsServices 0x01ff77e3 GSEventRunModal + 88
22 GraphicsServices 0x01ff7668 GSEventRun + 104
23 UIKit 0x0043c65c UIApplicationMain + 1211
24 RenrenPhoto 0x000026b2 main + 178
25 RenrenPhoto 0x000025b5 start + 53
26 ??? 0x00000001 0x0 + 1
从调用栈上看,三次调用的路径都不相同,关键区分点在_UIApplicationHandleEvent函数中,
第一次的调用位置为_UIApplicationHandleEvent + 1696
第二次的调用位置为_UIApplicationHandleEvent + 2413
第三次的调用位置为_UIApplicationHandleEvent + 4896
canBecomeFirstResponder
方法让他返回YES
UIResponder 以及事件相关..还有第一响应对象,布布扣,bubuko.com
标签:style blog http color 使用 os strong 数据
原文地址:http://www.cnblogs.com/zhepama/p/3868933.html