zoukankan      html  css  js  c++  java
  • iOS事件传递&响应者链条

    原文:http://www.cnblogs.com/Quains/p/3369132.html

    主要是记录下iOS的界面触摸事件处理机制,然后用一个实例来说明下应用场景.

    一、处理机制

    界面响应消息机制分两块,(1)首先在视图的层次结构里找到能响应消息的那个视图。(2)然后在找到的视图里处理消息。

    【关键】(1)的过程是从父View到子View查找,而(2)是从找到的那个子View往父View回溯(不一定会往回传递消息)。

    1.1、寻找响应消息视图的过程可以借用M了个J的一张图来说明。

    处理原理如下:

    • 当用户点击屏幕时,会产生一个触摸事件,系统会将该事件加入到一个由UIApplication管理的事件队列中

    • UIApplication会从事件队列中取出最前面的事件进行分发以便处理,通常,先发送事件给应用程序的主窗口(UIWindow)

    • 主窗口会调用hitTest:withEvent:方法在视图(UIView)层次结构中找到一个最合适的UIView来处理触摸事件

    (hitTest:withEvent:其实是UIView的一个方法,UIWindow继承自UIView,因此主窗口UIWindow也是属于视图的一种)

    • hitTest:withEvent:方法大致处理流程是这样的:

    首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内:

    ▶ 若pointInside:withEvent:方法返回NO,说明触摸点不在当前视图内,则当前视图的hitTest:withEvent:返回nil

    ▶ 若pointInside:withEvent:方法返回YES,说明触摸点在当前视图内,则遍历当前视图的所有子视图(subviews),调用子视图的hitTest:withEvent:方法重复前面的步骤,子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图的hitTest:withEvent:方法返回非空对象或者全部子视图遍历完毕:

    ▷ 若第一次有子视图的hitTest:withEvent:方法返回非空对象,则当前视图的hitTest:withEvent:方法就返回此对象,处理结束

    ▷ 若所有子视图的hitTest:withEvent:方法都返回nil,则当前视图的hitTest:withEvent:方法返回当前视图自身(self)

    • 最终,这个触摸事件交给主窗口的hitTest:withEvent:方法返回的视图对象去处理。

    拿到这个UIView后,就调用该UIView的touches系列方法。

    1.2、消息处理过程,在找到的那个视图里处理,处理完后根据需要,利用响应链nextResponder可将消息往下一个响应者传递。

    UIAppliactionDelegate <- UIWindow <- UIViewController <- UIView <- UIView

    【关键】:要理解的有三点:1、iOS判断哪个界面能接受消息是从View层级结构的父View向子View传递,即树状结构的根节点向叶子节点递归传递。2、hitTest和pointInside成对,且hitTest会调用pointInside。3、iOS的消息处理是,当消息被人处理后默认不再向父层传递。

    二、应用实例

    【需求】是:界面如下,

    Window

      -ViewA

        -ButtonA

        -ViewB

          -ButtonB

    层次结构:ViewB完全盖住了ButtonA,ButtonB在ViewB上,现在需要实现1)ButtonA和ButtonB都能响应消息 2)ViewA也能收到ViewB所收到的touches消息 3)不让ViewB(ButtonB)收到消息。

    (首先解析下,默认情况下,点击了ButtonB的区域,iOS消息处理过程。

    -ViewA 

      -ButtonA

      -ViewB

        -ButtonB

    当点击ButtonB区域后,处理过程:从ViewA开始依次调用hitTest

    pointInside的值依次为:

    ViewA:YES;

    ViewB:YES;

    ButtonB:YES;

    ButtonB的subViews:NO;

    所以ButtonB的subViews的hitTest都返回nil,于是返回的处理对象是ButtonB自己。接下去开始处理touches系列方法,这里是调用ButtonB绑定的方法。处理完后消息就停止,整个过程结束。)

    【分析】:

    实现的方式多种,这里将两个需求拆解开来实现,因为实现2就可以满足1。

    2.1、需求1的实现,ViewB盖住了ButtonA,所以默认情况下ButtonA收不到消息,但是在消息机制里寻找消息响应是从父View开始,所以我们可以在ViewA的hitTest方法里做判断,若touch point是在ButtonA上,则将ButtonA作为消息处理对象返回。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #pragma mark - hitTest
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        // 当touch point是在_btn上,则hitTest返回_btn
        CGPoint btnPointInA = [_btn convertPoint:point fromView:self];
        if ([_btn pointInside:btnPointInA withEvent:event]) {
            return _btn;
        }
         
        // 否则,返回默认处理
        return [super hitTest:point withEvent:event];
         
    }

    这样,当触碰点是在ButtonA上时,则touch消息就被拦截在ViewA上,ViewB就收不到了。然后ButtonA就收到touch消息,会触发onClick方法。

    2.2、需求2的实现,上面说到响应链,ViewB只要override掉touches系列的方法,然后在自己处理完后,将消息传递给下一个响应者(即父View即ViewA)。

    代码如下:在ViewB代码里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #pragma mark - touches
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"B - touchesBeagan..");
         
        // 把事件传递下去给父View或包含他的ViewController
        [self.nextResponder touchesBegan:touches withEvent:event];
    }
     
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"B - touchesCancelled..");
        // 把事件传递下去给父View或包含他的ViewController
        [self.nextResponder touchesBegan:touches withEvent:event];
    }
     
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"B - touchesEnded..");
        // 把事件传递下去给父View或包含他的ViewController
        [self.nextResponder touchesBegan:touches withEvent:event];
    }
     
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"B - touchesMoved..");
        // 把事件传递下去给父View或包含他的ViewController
        [self.nextResponder touchesBegan:touches withEvent:event];
         
    }

    然后,在ViewA上就可以接收到touches消息,在ViewA上写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #pragma mark - touches
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"A - touchesBeagan..");
    }
     
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"A - touchesCancelled..");
    }
     
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"A - touchesEnded..");
    }
     
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"A - touchesMoved..");
         
    }

    这样就实现了向父View透传消息。

    2.3 、不让ViewB收到消息,可以设置ViewB.UserInteractionEnable=NO;除了这样还可以override掉ViewB的ponitInside,原理参考上面。

    在ViewB上写:

    1
    2
    3
    4
    5
    6
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
        // 本View不响应用户事件
        return NO;
      
    }

     

    我的Github iOS:https://github.com/BigShow1949/BigShow1949 Java:https://github.com/BigShow1949 每周都会更新,求个小星星! 我的QQ:1029883589 QQ群:148279151
  • 相关阅读:
    Read-Copy Update Implementation For Non-Cache-Coherent Systems
    10 华电内部文档搜索系统 search04
    10 华电内部文档搜索系统 search05
    lucene4
    10 华电内部文档搜索系统 search01
    01 lucene基础 北风网项目培训 Lucene实践课程 索引
    01 lucene基础 北风网项目培训 Lucene实践课程 系统架构
    01 lucene基础 北风网项目培训 Lucene实践课程 Lucene概述
    第五章 大数据平台与技术 第13讲 NoSQL数据库
    第五章 大数据平台与技术 第12讲 大数据处理平台Spark
  • 原文地址:https://www.cnblogs.com/bigshow1949/p/5302765.html
Copyright © 2011-2022 走看看