UIResponder 以及事件相关..还有第一响应对象

在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 对象

第一响应对象  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 方法用来重新载入输入视图。

hitTest

在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.

一. hitTest:withEvent:调用过程

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:方法的处理流程如下:

  1. 首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
  2. 若返回NO,则hitTest:withEvent:返回nil;
  3. 若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
  4. 若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
  5. 如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

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:方法

如果父视图需要对对哪个子视图可以响应触摸事件做特殊控制,则可以重写hitTest:withEvent:pointInside:withEvent:方法。

这里有几个例子:

  1. hitTest Hacking the responder chain
    在此例子中button,scrollview同为topView的子视图,但scrollview覆盖在button之上,这样在在button上的触摸操作返回的hit-test view为scrollview,button无法响应,可以修改topView的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;
    }

    这样如果触摸点在button的范围内,返回hittestView为button,从button按钮可以响应点击事件。

  2. Paging-enabled UIScrollView with Previews
    BSPreviewScrollView
    关于这两个例子,可以看之前文章的说明,见Paging-enabled UIScrollView

三.hitTest:withEvent:探秘,诡异的三次调用

在测试中发现对每一次触摸操作实际会触发三次hitTest:withEvent:方法调用,有测试环境视图结构如下 UIWindow->UIScrollView->TapDetectingImageView

    1. 首先在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: {()}

      三次调用的point参数完全相同,event从指针看为同一对象,但时间戳有变化,第一次和第二次的时间戳相同,第三次的与之前有区别。

    2. 深入检查event参数
      对于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事件类型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才是针对当前点击的。

    3. 调用栈分析
      使用
      [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

    4. 结论
      没有结论,具体机制仍然是扑朔迷离,搞不懂….,实际写代码时也不需要考虑这些。

Responder Chain

responder chain是由一系列responder对象连接起来的,他从第一个responder对象开始一直到application对象结束。如果第一个responder不能够处理该事件则该事件会被发送到下一个在该responder chain中的responder来处理。

当自己定义的一个类想让他成为first responder时需要做两件事:

1.重写 canBecomeFirstResponder 方法让他返回YES

2.接受 becomeFirstResponder 消息,如果必要的话可让对象给自己发送该消息。

在这里有一个地方需要注意,当把一个对象变为first responder是要确保这个对象的图形界面已经建立起来,也就是说要在viewDidAppear中调用becomeFirstResponder方法。如果在veiwWillAppear方法中调用becomeFirstResponder将会得到一个NO。

Responder Chain 遵循一个特定的传播路径

如果hit-test view不能够处理该事件则UIKit会将事件传递给下一个Responder。下图则显示了事件在Responder Chain中传播的两种方式:

对于左边的app中事件传播路径如下:

1.初始的界面尝试去处理事件后者消息,打他处理不了则把事件交给它上一层视图处理,因为最开始的界面在他的view controller里的视图层次里不是最上层的。(这里的上下是按照树的结构而言的,下图解释:)

2.上层视图尝试处理事件,如果他不能处理则将事件交给他的上层视图处理,原因同上。

3.在view controller中最上层的视图尝试处理,他也不能处理则交给他的view controller来处理。

4.view controller也无法处理则交给window来处理。

5.window无法处理交给app object来处理

6.app object无法处理则将该事件丢弃掉。

右边的传播方式稍有不同:

1.一个视图在他的view controller 的视图层中向上传播一个事件直到它到达最顶层视图。

2.最顶层视图无法处理将event交给他的view controller来处理。

3.view controller 传递事件到他的最顶层视图的上一层视图,接下来重复1-3的步骤直到事件到达root view controller。

4.root view controller将事件传递到window object。

5.window 将事件传递给app object。

注意:事件,消息不要自己向上传送而要调用父类中的方法来处理,让UIKit来处理消息在responder chain中的传递。

UIResponder 以及事件相关..还有第一响应对象

时间: 2024-08-14 02:50:32

UIResponder 以及事件相关..还有第一响应对象的相关文章

iOS 键盘第一响应

. UIResonder 对于C#里所有的控件(例如TextBox),都继承于Control类.而Control类的继承关系如 下: System.Object System.MarshalByRefObject System.ComponentModel.Component System.Windows.Forms.Control 对于iOS里的UI类,也有类似的继承关系. 例如对于UITextField,继承于UIControl:UIControl继承于UIView,UIView继承于UIR

史上最详细的iOS之事件的传递和响应机制

前言: 按照时间顺序,事件的生命周期是这样的: 事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view.寻找最合适的view的底层实现.拦截事件的处理)->找到最合适的view后事件的处理(touches方法的重写,也就是事件的响应) 其中重点和难点是: 1.如何寻找最合适的view 2.寻找最合适的view的底层实现(hitTest:withEvent:底层实现) (一)iOS中的事件 iOS中的事件可以分为3大类型: 触摸事件 加速计事件 远程控制事件这里我们只讨论iOS中的

事件的传递和响应机制(全)

前言: 按照时间顺序,事件的生命周期是这样的: 事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view.寻找最合适的view的底层实现.拦截事件的处理)->找到最合适的view后事件的处理(touches方法的重写,也就是事件的响应) 其中重点和难点是: 1.如何寻找最合适的view 2.寻找最合适的view的底层实现(hitTest:withEvent:底层实现) (一)iOS中的事件 iOS中的事件可以分为3大类型: 触摸事件 加速计事件 远程控制事件 这里我们只讨论iOS中

关于v4包的Fragment过渡动画的事件监听无响应问题解决

项目中部分功能模块采用了单Activity+多Fragment模式,当Fragment切换时,需要在过渡动画执行完后做一些操作,通常就是在自己封装的FragmentBase中重写onCreateAnimation方法,创建一个Animation对象,并添加动画的事件监听.而最近升级了v4包后,突然发现添加的动画事件监听无响应了.通过查看源码,发现在v4包中关于Fragment管理类FragmentManagerImpl中,在获取Animation对象后,也添加了对动画的监听事件,也就覆盖了我自己

js事件相关知识点总结

HTML页面是怎样实现交互的? 2017-05-22 js事件之事件流: 事件流原理图:事件流是从window开始,最后回到window的一个过程,分为三个阶段(1~5)捕获过程.(5~6)目标过程.(6~10)冒泡过程. 冒泡型事件:事件按照从最具体的事件目标到最不具体的事件目标(document对象)向上传播的顺序触发. IE 5.5: div -> body -> document IE 6.0: div -> body -> html -> document Mozi

事件相关的-内存和性能

由于事件处理程序带来的交互能力,在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能. 导致这一问题原因有哪些: 事件相关的处理函数越多,所占用的内存越多,函数是对象,而对象就会占据内存,从而占据内存空间.而内存越多性能就越差. 其次必须事先指定事件处理程序,则需要访问的dom次数增多,会延迟整个页面的交互就绪时间. 事实上从事件处理程序的角度出发,还有那些方法可以提升性能的? 事件委托 由于在一些常用事件是支持冒泡的,所以可以在其父节点或者其祖先节点上获取到对

Vue 事件相关实例方法---on/emit/off/once

一.初始位置 平常项目中写逻辑,避免不了注册/触发各种事件 今天来研究下 Vue 中,我们平常用到的关于 on/emit/off/once 的实现原理 关于事件的方法,是在 Vue 项目下面文件中的 eventsMixin 注册的 src/core/instance/index.js import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './rend

Jquery基础入门-3-JQuery事件相关介绍

14-JQuery事件相关介绍-132:20 15-JQuery事件相关介绍-233:19 <strong>一.$(document).ready()和window.onload方法区别?</strong><br /> 1. 执行时机不同:<br /> ◆ window.onload方法是在网页中的元素(包括元素的所有关联文件)完全加载到浏览器后执行,即JavaScript此时才可以访问网页中的任何元素.<br /> ◆ 而$(document)

深入理解DOM事件机制系列第一篇——事件流

× 目录 [1]历史 [2]事件冒泡 [3]事件捕获[4]事件流 前面的话 javascript操作CSS称为脚本化CSS,而javascript与HTML的交互是通过事件实现的.事件就是文档或浏览器窗口中发生的一些特定的交互瞬间,而事件流(又叫事件传播)描述的是从页面中接收事件的顺序.本文将详细介绍该部分的内容 历史 当浏览器发展到第四代时(IE4及Netscape4),浏览器开发团队遇到了一个很有意思的问题:页面的哪一部分会拥有某个特定的事件?想象画在一张纸上的一组同心圆.如果把手指放在圆心