iOS的事件相应分发

移动平台上的开发主要关注数据以及数据的处理,事件的处理以及UI。所以事件的分发处理是很重要的一个环节,对于一个平台的优劣来说也是一项重要的参数。如果事件的分发设计的不好,一些复杂的UI场景就会变得很难写甚至没法写。从小屏没有触摸的功能机开始到现在大屏多点触摸的智能机,对于事件的分发处理基本思路都是一样的——链(设计模式中有个模式就是职责链chain of responsibility),只是判定的复杂程度不同。
iOS中的事件有3类,触摸事件(单点,多点,手势)、传感器事件(加速度传感器)和远程控制事件,这里我介绍的是第一种事件的分发处理。



上面的这张图来自苹果的官方。描述了Responder的链,同时也是事件处理的顺序。通过这两张图,我们可以发现:
1. 事件顺着responder chain传递,如果一环不处理,则传递到下一环,如果都没有处理,最后回到UIApplication,再不处理就会抛弃
2. view的下一级是包含它的viewController,如果没有viewController则是它的superView
3. viewController的下一级是它的view的superView
4. view之后是window,最后传给application,这点iOS会比OS X简单(application就一个,window也一个)
总结出来传递规则是这样的:



这样事件就会从first responder逐级传递过来,直到被处理或者被抛弃。
    由于UI的复杂,这个responder chain是需要根据事件来计算的。比如,我现在在一个view内加入了2个Button,先点击了一个,则first responder肯定是这个点击过的button,但我下面可以去点击另一个button,所以显然,当触摸事件来时,这个chain是需要重新计算更新的,这个计算的顺序是事件分发的顺序,基本上是分发的反过来。
    ![](http://upload-images.jianshu.io/upload_images/3635066-333c04a606d767d5?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    无论是哪种事件,都是系统本身先获得,是iOS系统来传给UIApplication的,由Application再决定交给谁去处理,所以如果我们要拦截事件,可以在UIApplication层面或者UIWindow层面去拦截。
    ![](http://upload-images.jianshu.io/upload_images/3635066-f90223d001d55e6f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    UIView是如何判定这个事件是否是自己应该处理的呢?iOS系统检测到一个触摸操作时会打包一个UIEvent对象,并放入Application的队列,Application从队列中取出事件后交给UIWindow来处理,UIWindow会使用hitTest:withEvent:方法来递归的寻找操作初始点所在的view,这个过程成为hit-test view。
    hitTest:withEvent:方法的处理流程如下:调用当前view的pointInside:withEvent:方法来判定触摸点是否在当前view内部,如果返回NO,则hitTest:withEvent:返回nil;如果返回YES,则向当前view内的subViews发送hitTest:withEvent:消息,所有subView的遍历顺序是从数组的末尾向前遍历,直到有subView返回非空对象或遍历完成。如果有subView返回非空对象,hitTest方法会返回这个对象,如果每个subView返回都是nil,则返回自己。
    好了,我们还是看个例子:
    ![](http://upload-images.jianshu.io/upload_images/3635066-6f552b76d4c290ca?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    这里ViewA包含ViewB和ViewC,ViewC中继续包含ViewD和ViewE。假设我们点击了viewE区域,则hit-test View判定过程如下:
   1. 触摸在A内部,所以需要检查B和C
   2. 触摸不在B内部,在C内部,所以需要检查D和E
   3. 触摸不在D内部,但在E内部,由于E已经是叶子了,所以判定到此结束

    我们可以运行一段代码来验证,首先从UIView继承一个类myView,重写里面的

[objc] view plain copy

  • (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
    UIView *retView = nil;
    NSLog(@"hitTest %@ Entry! event=%@", self.name, event);

    retView = [super hitTest:point withEvent:event];
    NSLog(@"hitTest %@ Exit! view = %@", self.name, retView);

    return retView;
    }

[objc] view plain copy

  • (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
    BOOL ret = [super pointInside:point withEvent:event];
    // if ([self.name isEqualToString:@"viewD"]) {
    // ret = YES;
    // }
    if (ret) {
    NSLog(@"pointInside %@ = YES", self.name);
    } else {
    NSLog(@"pointInside %@ = NO", self.name);
    }

    return ret;
    }

      在viewDidLoad方法中手动加入5个view,都是myView的实例。
    

[objc] view plain copy

  • (void)viewDidLoad
    {
    [super viewDidLoad];

    _viewA = [[myView alloc] initWithFrame:CGRectMake(10, 10, 300, 200) Color:[UIColor blackColor] andName:@"viewA"];
    [self.view addSubview:_viewA];
    [_viewA release];

    _viewB = [[myView alloc] initWithFrame:CGRectMake(10, 240, 300, 200) Color:[UIColor blackColor] andName:@"viewB"];
    [self.view addSubview:_viewB];
    [_viewB release];

    _viewC = [[myView alloc] initWithFrame:CGRectMake(10, 10, 120, 180) Color:[UIColor blueColor] andName:@"viewC"];
    [_viewB addSubview:_viewC];
    [_viewC release];

    _viewD = [[myView alloc] initWithFrame:CGRectMake(170, 10, 120, 180) Color:[UIColor blueColor] andName:@"viewD"];
    [_viewB addSubview:_viewD];
    [_viewD release];

    _viewE = [[myView alloc] initWithFrame:CGRectMake(30, 40, 60, 100) Color:[UIColor redColor] andName:@"viewE"];
    [_viewD addSubview:_viewE];
    [_viewE release];

}

    这个样式如下:

当我点击viewE的时候,打印信息如下:

2014-01-25 18:32:46.538 eventDemo[1091:c07] hitTest viewB Entry! event=<UITouchesEvent: 0x8d0cae0> timestamp: 6671.26 touches: {(
)}
2014-01-25 18:32:46.538 eventDemo[1091:c07] pointInside viewB = YES
2014-01-25 18:32:46.539 eventDemo[1091:c07] hitTest viewD Entry! event=<UITouchesEvent: 0x8d0cae0> timestamp: 6671.26 touches: {(
)}
2014-01-25 18:32:46.539 eventDemo[1091:c07] pointInside viewD = YES
2014-01-25 18:32:46.539 eventDemo[1091:c07] hitTest viewE Entry! event=<UITouchesEvent: 0x8d0cae0> timestamp: 6671.26 touches: {(
)}
2014-01-25 18:32:46.540 eventDemo[1091:c07] pointInside viewE = YES
2014-01-25 18:32:46.540 eventDemo[1091:c07] hitTest viewE Exit! view = <myView: 0x8c409f0; frame = (30 40; 60 100); layer = <CALayer: 0x8c40a90>>
2014-01-25 18:32:46.540 eventDemo[1091:c07] hitTest viewD Exit! view = <myView: 0x8c409f0; frame = (30 40; 60 100); layer = <CALayer: 0x8c40a90>>
2014-01-25 18:32:46.541 eventDemo[1091:c07] hitTest viewB Exit! view = <myView: 0x8c409f0; frame = (30 40; 60 100); layer = <CALayer: 0x8c40a90>>
2014-01-25 18:32:46.541 eventDemo[1091:c07] touchesBegan viewE
2014-01-25 18:32:46.624 eventDemo[1091:c07] touchesEnded viewE
从打印信息可以看到,先判断了viewB,然后是viewD,最后是viewE,但事件就是直接传给了viewE。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容