了解IOS事件处理的本质关键要先掌握几个概念。首先是 事件的派发(Event Delivery)的过程 , 一个是 响应者链条如何构成 。
Q1: 你又没有想过,如果你一个屏幕中有多个的View。当你点击某个view的时候, 这个点击事件是如何传递到这个View身上的?
S1: 正是因为当我们点击屏幕上某个点的时候, IOS会检查到手指触摸操作(Touch),并生产一个UITouch对象,将其打包成一个UIEvent对象。然后将其放入当前活动的Application的 事件对列 , UIApplication会从 事件对列 中按照对列的顺序,取出触摸事件传递给UIWindow处理,UIWindow对象会使用hitTest:withEvent:方法来寻找此次的触摸操作初始点所在的最深层次的视图(View). ** 即调用hitTest:withEvent会返回该触摸点所在的最深层次的视图。 **
Q3:既然hitTest:withEvent利用了深度优先的思想来做,并采用递归的做法来做。那么递归的条件是啥?也就是说什么条件下事件不会向下派发?
Q4: 也许你经常听别人在说响应者链条,但是还是云里雾里。这边我就给你解释下
S4: 首先先明确何为响应者? ===> 在ios开发中继承自UIResponder的类或子类就是响应者,顾明思意,响应者是用来相应事件的(触摸事件、运动事件、远程遥控事件)。所以所谓的 响应者链条就是一系列响应者构成的层次结构。
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而是只响应这个事件。
#import "SWPButton.h" @implementation SWPButton - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesBegan:touches withEvent:event]; } @end
所以此时如果你点击按钮, 其会先调用这个自定义按钮的touchesBegan, 因为点击事件传递到了button身上,所以会调用touchesBegan来响应该事件,但是该事件不在交给父类处理,所以不会调用action。只会继续将其抛给上一个响应者。
@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主要实现的功能是,传递事件,找到最合适的视图。