以前遇到一个项目,一个uiimageview对象上面有一个uibutton对象,然而项目的需求需要在点击 button的同时,uiimageview也接收到点击事件,在不使用代理和通知方法的前提下,通过事件响应链的原理,我们也可以很便捷的解决这个问题。
在处理这个问题之前,我们应该先清楚ios的事件响应机制到底是个什么样的原理。
首先,这个事件响应的机制是分为两个部分的。
1、先在视图层级关系中找到应该响应事件的那个视图。
这一步是什么意思,其实很简单,就是找到你所触摸点对应的那个最上层的视图,它的工作原理是这样的:当用户发出事件后,会产生一个触摸事件,系统会将该事件加入到一个由uiapplication管理的事件队列中,uiapplication会取出队列中最前面的事件,发消息给uiwindow,然后uiwindow会对其所有子视图调用hittest:withevent:这个方法,这个方法会返回一个uiview的对象,这个方法在执行的时候,它会调用当前视图的pointinside:withevent:这个方法,如果触摸事件在当前视图范围内,pointinside:withevent:会返回yes,否则会返回no;如果返回yes,则会遍历当前视图的所有子视图,统统发送hittest:withevent:这个消息,如果返回no,则hittest:withevent:方法返回nil;
上面说起来有些绕,其实就是:hittest:withevent:方法会一层一层的向上找,若最上层响应的子视图pointinside:withevent:返回yes,则返回此子视图,如果所有的都返回nil,则返回当前视图本身self。
例如:我们建两个文件,一个继承于uibutton,一个继承于uiimageview,我们在uiimageview里的代码如下:
<a href="http://my.oschina.net/u/2340880/blog/396161#">?</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<code>#import "myimageview.h"</code>
<code>@implementation myimageview</code>
<code>- (instancetype)initwithframe:(cgrect)frame</code>
<code>{</code>
<code> </code><code>self = [super initwithframe:frame];</code>
<code> </code><code>if</code> <code>(self) {</code>
<code> </code><code>self.backgroundcolor=[uicolor redcolor];</code>
<code> </code><code>}</code>
<code> </code><code>return</code> <code>self;</code>
<code>}</code>
<code>//在这里,我们重写了这个方法,让它直接返回自身,而不是继续向下寻找应该响应事件的视图</code>
<code>-(uiview *)hittest:(cgpoint)point withevent:(uievent *)event{</code>
<code>-(</code><code>void</code><code>)touchesbegan:(nsset *)touches withevent:(uievent *)event{</code>
<code> </code><code>nslog(@</code><code>"点击了image"</code><code>);</code>
然后将他们创建在一个view上:
<code>- (</code><code>void</code><code>)viewdidload {</code>
<code> </code><code>[super viewdidload];</code>
<code> </code><code>myimageview * image = [[myimageview alloc]initwithframe:cgrectmake(60, 80, 200, 200)];</code>
<code> </code><code>mybutton * btn =[uibutton buttonwithtype:uibuttontypesystem];</code>
<code> </code><code>btn.frame=cgrectmake(20, 20, 40, 40);</code>
<code> </code><code>[btn settitle:@</code><code>"button"</code> <code>forstate:uicontrolstatenormal];</code>
<code> </code><code>[image addsubview:btn];</code>
<code> </code><code>[self.view addsubview:image];</code>
<code> </code><code>// do any additional setup after loading the view, typically from a nib.</code>
可以证明,在事件视图寻找中,uiimageview我们重写hittest:withevent:方法后,切断了寻找链,如果我们这个做:
<code> </code><code>return</code> <code>nil;</code>
你会发现,uiimageview也不再接收事件。
2、寻找到应该响应的视图后,会进行消息处理,这个处理的方式是通过消息处理链来做的。如果它自身不能处理消息,会通过nextresponder将消息传递给下一个处理者,默认只要有一个view将消息处理了,这个消息处理传递链将不再传递。
现在,我们把刚才uiimageview里重写的hittest:withevent:方法注释掉,给btn添加一个点击方法,同时将用户交互关闭:
<code> </code><code>image.userinteractionenabled=yes;</code>
<code> </code>
<code> </code><code>[btn addtarget:self action:@selector(click) forcontrolevents:uicontroleventtouchupinside];</code>
<code> </code><code>btn.userinteractionenabled=no;</code>
<code>-(</code><code>void</code><code>)click{</code>
<code> </code><code>nslog(@</code><code>"btn被点击了"</code><code>);</code>
这样,我们的uiimageview又可以响应事件了,原因是事件处理传递链向下传递了。
现在,在回到我们刚开始的问题,如何让btn响应的同时imageview也响应,我们这样做:
20
21
22
<code> </code><code>[btn addtarget:self action:@selector(click:) forcontrolevents:uicontroleventtouchupinside];</code>
<code> </code><code>btn.userinteractionenabled=no;</code>
<code>-(</code><code>void</code><code>)click:(uibutton *)btn{</code>
<code> </code><code>//响应链继续传递</code>
<code> </code><code>[btn.nextresponder touchesbegan:nil withevent:nil];</code>
结果如下:
虽然最终,我们完成了这个需求,可是我建议你最好不要这么干,因为这样的逻辑是违背现实生活中人们的行为认知的,更重要的是,我们的项目最后也确实改掉了这样的逻辑~~~