在一個(gè)app中間有一個(gè)button趣苏,在你手觸摸屏幕點(diǎn)擊后枪汪,到這個(gè)button收到點(diǎn)擊事件,中間發(fā)生了什么呢?
上面這個(gè)問(wèn)題回官,已經(jīng)成了一道經(jīng)典的面試題啦第献,今天我在某個(gè)頁(yè)面布局的時(shí)候衬吆,發(fā)現(xiàn)button的點(diǎn)擊事件一直無(wú)效秸滴,把我知道會(huì)犯的錯(cuò)都嘗試了一遍,然而還是沒(méi)用栈源,困擾我老久了挡爵,趁此機(jī)會(huì)再次把UIButton的響應(yīng)鏈再了解一遍。
在此我們需要先了解一下甚垦,UIResponder茶鹃,也許我們很少會(huì)直接用到它,但是基本上我們所能看到的所有圖形界面都是繼承自它的哦艰亮,它掌管著操作事件分發(fā)大權(quán)闭翩。
拿上述 Button 舉例, 此處用一下 nextResponder
:
- (IBAction)buttonAction:(id)sender {
UIButton *button = (UIButton *)sender;
NSLog(@"%@",button);
NSLog(@"%@",button.nextResponder);
NSLog(@"%@",button.nextResponder.nextResponder);
NSLog(@"%@",button.nextResponder.nextResponder.nextResponder);
NSLog(@"%@",button.nextResponder.nextResponder.nextResponder.nextResponder);
NSLog(@"%@",button.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder);
}
2016-07-08 22:25:56.268 TestButton[55893:12560428] <UIButton: 0x7faad3f1b850; frame = (184 353; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7faad3f1bdc0>>
2016-07-08 22:25:56.269 TestButton[55893:12560428] <UIView: 0x7faad3d27ca0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x7faad3d0a0d0>>
2016-07-08 22:25:56.269 TestButton[55893:12560428] <ViewController: 0x7faad3c65ed0>
2016-07-08 22:25:56.269 TestButton[55893:12560428] <UIWindow: 0x7faad3e51ab0; frame = (0 0; 414 736); autoresize = W+H; gestureRecognizers = <NSArray: 0x7faad3c67380>; layer = <UIWindowLayer: 0x7faad3c63e30>>
2016-07-08 22:25:56.269 TestButton[55893:12560428] <UIApplication: 0x7faad3c049e0>
2016-07-08 22:25:56.269 TestButton[55893:12560428] <AppDelegate: 0x7faad3d19050>
同時(shí)也可以看出這個(gè)最基本button 的響應(yīng)鏈。注意此處是通過(guò)[self.nextResponder touchesBegan:touches withEvent:event] 傳遞給下一個(gè) nextResponder 的迄埃,一般我們手動(dòng)重寫(xiě)了 touch 事件時(shí)就有可能中斷它的過(guò)程疗韵,當(dāng)然很多時(shí)候是用來(lái)監(jiān)聽(tīng)觀察的。
然后UIApplication接收到手指的事件之后侄非,就會(huì)去調(diào)用UIWindow的hitTest:withEvent:蕉汪,看看當(dāng)前點(diǎn)擊的點(diǎn)是不是在window內(nèi)流译,如果是則繼續(xù)依次調(diào)用subView的hitTest:withEvent:方法,直到找到最后需要的view者疤。
/** point :是否在view的frame范圍內(nèi)福澡, event: 傳過(guò)來(lái)的UITouchEvent*/
// 該方法用來(lái)判斷點(diǎn)擊事件發(fā)生的位置是否處于當(dāng)前視圖范圍內(nèi),以確定用戶是不是點(diǎn)擊了當(dāng)前視圖
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
// 若上述方法返回YES,則向當(dāng)前視圖的所有子視圖(subviews)發(fā)送下面該事件驹马,直到有子視圖返回非空對(duì)象或者全部子視圖遍歷完畢
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
/**
如所有子視圖都返回非革砸,則hitTest:withEvent:方法返回自身(self)。
也就是找到了最后需要的 View.
*/
官方一點(diǎn)的解釋hit-test view : 手指觸摸(Touch)操作時(shí)會(huì)將其打包成一個(gè)UIEvent對(duì)象糯累,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列算利,單例的UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來(lái)處理,UIWindow對(duì)象首先會(huì)使用hitTest:withEvent:方法尋找此次Touch操作初始點(diǎn)所在的視圖(View)寇蚊,即需要將觸摸事件傳遞給其處理的視圖笔时,這個(gè)過(guò)程稱之為hit-test view。
注意: hitTest里面是有判斷當(dāng)前的view是否支持點(diǎn)擊事件仗岸,比如userInteractionEnabled
、hidden
借笙、alpha
等屬性扒怖,都會(huì)影響一個(gè)view是否可以相應(yīng)事件,如果不響應(yīng)則直接返回nil业稼。 所以常常我們一個(gè)點(diǎn)擊事件不能被除非通常也可能是上述幾種原因之一盗痒。
同時(shí)更詳細(xì)的iOS事件響應(yīng)鏈中Hit-Test View的應(yīng)用, 推薦看看,作者舉例說(shuō)明了幾個(gè)常用的擴(kuò)展低散,Hit-test view的應(yīng)用舉例還是不錯(cuò)的俯邓。
接下去,此時(shí)熔号,我們已經(jīng)找到了最終的 view啦稽鞭,看它具體需要做什么啦
[button addTarget:self action:@selector(buttonTapDoSome) forControlEvents:UIControlEventTouchUpInside];
這個(gè)控件對(duì)象去觸發(fā)target對(duì)象上的action行為,來(lái)最終處理事件引镊。所以此處有順便了解下Target-Action朦蕴,Target-Action機(jī)制由兩部分組成:即目標(biāo)對(duì)象和行為Selector。目標(biāo)對(duì)象指定最終處理事件的對(duì)象弟头,而行為Selector則是處理事件的方法吩抓。
最后事件處理完成后,整個(gè)過(guò)程也就基本完成啦赴恨。
- 注意不能響應(yīng)的情況
1疹娶、userInteractionEnabled 交互是否打開(kāi)(本身和父視圖都要注意)
2、frame 子視圖的frame是否有超過(guò) 父視圖
3伦连、hidden 和 alpha也有可能
ps:我之前犯的錯(cuò)與這個(gè)不太對(duì)雨饺,是兩個(gè)對(duì)象之間無(wú)法響應(yīng)對(duì)方事件挣饥,從而導(dǎo)致無(wú)法傳遞事件。
備注參考:
https://www.zybuluo.com/MicroCai/note/66142
http://southpeak.github.io/blog/2015/12/13/cocoa-uikit-uicontrol/