前言

了解IOS事件处理的本质关键要先掌握几个概念。首先是事件的派发(Event Delivery)的过程, 一个是响应者链条如何构成

事件的派发:

Q1: 你有没有想过,如果你一个屏幕中有多个的View。当你点击某个view的时候, 这个点击事件是如何传递到这个View身上的?

S1: 正是因为当我们点击屏幕上某个点的时候, IOS会检查到手指触摸操作(Touch),并生产一个UITouch对象,将其打包成一个UIEvent对象。然后将其放入当前活动的Application的事件对列, UIApplication会从事件对列中按照对列的顺序,取出触摸事件传递给UIWindow处理,UIWindow对象会使用hitTest:withEvent:方法来寻找此次的触摸操作初始点所在的最深层次的视图(View).
**即调用hitTest:withEvent会返回该触摸点所在的最深层次的视图。 **

Q2:hitTest:withEvent如何实现找到最深层次的视图,也就是目的视图。

S2: 这就要说到深度优先搜索算法,hitTest:withEvent正是基于深度优先搜索的方式来找到最深层次的视图对象。所以我来介绍以下深度优先算法的思想, 要理解该思想, 你首先要有树结构这一概念(参见数据结构中的树结构)。该思想是从根节点开始遍历树,而遍历的顺序是采用把下一个子节点当做当前根节点继续遍历。
所以其是先遍历到最树的最深的一层再层层回朔到根节点,接着在把另外一个子节点当做当前根节点继续遍历。正是基于这种思想, 所以我们可以很方便的采用递归来实现。
如果还是不理解,有两种办法帮助你,一种是去找深度优先的动态图,一看就懂了我说的。另外一种方法是去复习数据结构与算法。

Q3:既然hitTest:withEvent利用了深度优先的思想来做,并采用递归的做法来做。那么递归结束的条件是啥?也就是说什么条件下事件不会向下派发?

S3:根据官方文档给出的条件是(hidden == YES || userInteractionEnabled == YES || alpha < 0.01 || subViews.bounds > subViews.superView.bounds)

何为响应者链条

Q4: 也许你经常听别人在说响应者链条,但是还是云里雾里。这边我就给你解释下

S4: 首先先明确何为响应者? ===> 在ios开发中继承自UIResponder的类或子类就是响应者,顾明思意,响应者是用来相应事件的(触摸事件、运动事件、远程遥控事件)。所以所谓的响应者链条就是一系列响应者构成的层次结构。

Q5: 那么响应者链条是如何表示这种层次结构的呢?

S5: 响应者链条是通过nextResponder方法的返回值来组成这种层次结构的 ,苹果有一段官方解释如下:

The UIResponder class does not store or set the next responder automatically, instead returning nil by default. Subclasses must override this method to set the next responder. UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t); UIViewController implements the method by returning its view’s superview; UIWindow returns the application object, and UIApplication returns nil.

也就是说,响应者对象是不会自动设置和存储下一个响应者,默认情况下是直接返回nil。而继承自UIResponder的子类必须重写这个方法来设置下一个响应者,并且需要遵循如下规范

    1. 如果子类是UIView,那么其getter方法的nextResponder必须返回其UIViewController对象。
如果不存在控制器,则返回其父视图对象。
2. 如果子类是UIViewController对象, 那么重写的nextResponder方法必须返回其view视图的父视图对象。
3. 如果子类是UIWindow对象,那么重写的nextResponder方法返回的是application对象
4. 如果子类是UIApplication对象,那么重写的nextResponder方法,返回nil。

通过上述规范,结合下图,你应该能很容易理解所谓的响应者链条如何构成:

Q6: 疑问,假设我有一个自定义的SWPButton,而且给其设置了连线action(也就是点击按钮后回调的函数)。我们知道当我们点击按钮的时候系统会捕捉到这个事件,并将其派发下来。那么我们如何做到让按钮不响应这个action而是只响应这个事件。

  • S6: 本质上系统默认实现的touchesBegan:withEvent:方法是做了两件事件, 或者说有继承子UIResponder的系统定义的类,都默认实现了以下两件事情

    • 第一件事情是: 调用[super touchesBegan:withEvent]让父类有机会来处理该事件。 同时传递了事件,让父类能根据事件处理相应的action回调,比如父类会在这里面处理action(也就是点击按钮时候的回调)
    • 第二件事情是: 抛给上一个响应者,让上一个响应者也能处理该事件。同时传递了该事件。
      这两个是不一样的,前者是针对父类,后者是针对响应者链条。二者缺一不可。
      再者,有的人会说只要实现了[super toucesBegan:withEveent]就会将事件抛开上一个响应者,这是不严谨的说法。因为这是因为系统已经为所有继承自UIResponder的子类,做了上面那两件事件。
      比如 ,当你自定义一个SWPButton该类继承自UIButton,那么当你重写touchesBegan方法的时候,在你们调用[super touchesBegan:withEvent]这个方法时候,系统自定义的UIButton类已经具备这个功能,所以
      其能完成向上抛。而当我们定义一个纯洁的SWPButton直接继承自UIResponder, 那么我们必须自己实现向上抛这个事情,而不是只调用[super touchesBegan:withEvent]。


      华丽的分割线,以下是度对Q6的解答
      因为我们继承自UIButton,所以要截断该action的回调,我们只需重写touchesBegan:withEvent方法。并且不做第一件事件,那么也就不会将事件传递给Button对象,button也就无法响应相应的action。
#import "SWPButton.h"
@implementation SWPButton
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesBegan:touches withEvent:event];
}
@end

所以此时如果你点击按钮, 其会先调用这个自定义按钮的touchesBegan, 因为点击事件传递到了button身上,所以会调用touchesBegan来响应该事件,但是该事件不在交给父类处理,所以不会调用action。只会继续将其抛给上一个响应者。

放大招(根据View来找到其所在控制器):

@implementation UIView (ParentController)
-(UIViewController*)parentController{
UIResponder *responder = [self nextResponder];
while (responder) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController*)responder;
}
responder = [responder nextResponder];
}
return nil;
}
@end
思路很简单,就是利用响应者链条来寻找UIViewController.
那么这个UIView的上一个响应者只有两种情况,一种是依然是一个UIView对象或其子对象,也就是说它是这个UIView的子View。
一种是这个 UIView的控制器。所以我们才需要循环判断,然后不断找(循环),直到第一次找到的控制器,就是这个UIView所在的控制器。
如果找不到,就返回nil。

是不是更加理解你事件派发的过程,所谓的事件派发过程,其实就是寻找最合适的视图的时候,事件随着这个寻找过程,不断传递。
为什么要传递UIEvent呢?因为通过它给以获得Touch对象,而通过Touch对象我们可以获得初始触摸点。也就是说hitTest:withEvent主要实现的功能是,传递事件,找到最合适的视图。

事件处理原理(IOS篇) by sixleaves的更多相关文章

  1. 再造轮子之网易彩票-第一季(IOS 篇 by sixleaves)

    前言 在网上看了别人做的模仿网易彩票的项目, 于是也跟着用自己的想法做了一篇.写这篇博客的目的, 在于UI综合的一次小练习, 同时总结和串联其各个控件之间的应用.封装思想等.考虑到有人上不了githu ...

  2. 再造轮子之网易彩票-第二季(IOS 篇 by sixleaves)

    02-彩票项目第二季 2.封装SWPTabBar方式一 接着我们思考如何进行封装.前面已经将过了为什么要封装, 和封装达到的效果.这里我们主要有两种封装方式,分别是站在不同的角度上看待问题.虽然角度不 ...

  3. 利用内存结构及多线程优化多图片下载(IOS篇)

    利用内存结构及多线程优化多图片下载(IOS篇) 前言 下载地址, 后续发布, 请继续关注本blog 在IOS中,我们常常遇到多图片下载的问题.最简单的解决方案是直接利用别人写好的框架.但是这如同练武, ...

  4. 将JSON映射为实体对象(iOS篇)

    将JSON映射为实体对象(iOS篇) iOS开发人员已经习惯于将JSON转换为字典或者数组来进行操作了,接下来我要做的事情,可能匪夷所思,但是,对WP和Android开发人员而言,他们更倾向于将JSO ...

  5. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  6. 调用MobileAPI的设计(iOS篇)

    调用MobileAPI的设计(iOS篇) 这一节讲如何发起网络请求. iOS用于调用MobileAPI的第三方组件很多,我们这里采用的是以下组件: 1)ASIHTTPRequest,用于请求Mobil ...

  7. 自定义生命周期的设计(iOS篇)

    自定义生命周期的设计(iOS篇) 首先要确定一点,我们的App,要基于XIB文件进行编程,而不是在每个相应的ViewController里面去手动创建页面的每个控件.这样做的好处是,将页面布局与业务逻 ...

  8. 项目结构的设计(iOS篇)

    项目结构的设计(iOS篇) 本文附带源码:YoungHeart-Chapter-02.zip 在设计任何一个框架之前,都应规划好项目结构. 假定Git作为我们的项目管理工具.我们要建立两个仓库,一个用 ...

  9. 做推送,怎么能不了解推送的 4 种消息形式呢?(iOS 篇)

    极光推送是为 App 提供第三方推送服务的平台之一,它提供四种消息形式:通知,自定义消息,富媒体和本地通知.笔者将基于官方说明与个人理解来谈一下这四种消息.本篇为 iOS 篇,Android 篇入口. ...

随机推荐

  1. Fence Repair (POJ 3253)

    农夫约翰为了修理栅栏,要将一块很长的木板切割成N块.准备切成的木板长度为L1.L2.L3...LN,未切割前的木板长度恰好为切割后木板长度的总和.每次切断木板时,需要的开销为这块木板的长度.例如长度为 ...

  2. 【每天一个Linux命令】19. 创建文件夹目录命令mkdir

    命令用途 mkdir 命令用来创建指定的名称的目录 使用说明 1.  创建目录的用户在当前目录中具有写权限 2. 指定的目录名不能是当前目录中已有的目录. 命令实例 0. 帮助文件 bixiaopen ...

  3. python3-day6(模块)

    一.OS模块 1.os.system('ls -l') #子shell运行,获取返回值,不是结果. 2.os.popen('ls -l').read() #获取结果. 二.sys模块 1.sys.ar ...

  4. python filecmp标准库基础学习

    # -*- coding: utf-8 -*-# 作者:新手__author__ = 'Administrator'#文件的比较import os,filecmp#作用用于比较系统中的目录和文件#例子 ...

  5. Javascript:作用域 学习总结

    作用域(scope): 变量与函数的可访问范围,控制着变量与函数的可见性和生命周期   作用域分类: javascript中,变量的作用域分为:全局作用域,局部作用域 局部变量的优先级大于全局变量,或 ...

  6. jQuery插件开发 格式与解析

    jQuery插件的开发包括两种: 一种是类级别的插件开发,即给jQuery添加新的全局函数,相当于给jQuery类本身添加方法.jQuery的全局函数就是属于jQuery命名空间的函数,另一种是对象级 ...

  7. ASP.NET 实现PDF文件下载

    本文介绍了一种在ASP.NET中下载文件的方法. 方法一:可能是最简单的.最短的方式: Response.ContentType = "application/pdf"; Resp ...

  8. 自定义seekbar中,thumb被覆盖掉一部分问题

  9. 如何单独编译Android源代码中的模块

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6566662 第一次下载好Android源代码工 ...

  10. C++11 静态断言(static_assert)

      简介 C++0x中引入了static_assert这个关键字,用来做编译期间的断言,因此叫做静态断言. 其语法很简单:static_assert(常量表达式,提示字符串). 如果第一个参数常量表达 ...