IOS學習可以先從基礎開始,以後會介紹寫内容吧
這篇文章想跟大家分享的主旨是iOS捕獲使用者事件的各種情況,以及内部封裝的一些特殊事件。
我們先從UIButton談起,UIButton大家使用的太多了,他特殊的地方就在于其内置的普通Default/高亮Highlighted/選擇Selected/可用Enable的幾個狀态(UIControlState)。其次就是SDK内部已經為我們封裝了以下使用者事件:

最常用的莫過于TouchUpInside這個事件了,他代表:使用者在按鈕區域内按下,并且也在按鈕區域内松開。
關鍵點:按下并且松開才能觸發此方法,也就是正确的操作按下一次,松開一次隻會觸發一次此事件。與之不同的TouchDragInside等方法不需要松開這個過程,Up變為了Drag,其實大家都能了解,SDK在封裝的時候原理跟UITouchEvent是一個道理,第一個單詞Touch代表按下(Began)第二個單詞Up代表松開(Ended),Drag代表拖動(Moved)。TouchMoved方法在一次完整的觸摸中會被觸發很多次,是以TouchDragInside方法會在使用者手松開之前一直被觸發。
這些就是UIButton已封裝的事件,而UIButton繼承自UIControl。UIControl又繼承自UIView。我們平時能用這些已封裝的事件的控件都是UIControl的子類。那麼父類UIView是沒有内部事件的。
我們常常利用UIView來寫自己的UITouchEvent。例如在一個View/ViewController中直接實作以下3個方法:
[cpp]viewplaincopyprint?
-(void)touchesBegan:(NSSet*)toucheswithEvent:(UIEvent*)event
{
}
-(void)touchesMoved:(NSSet*)toucheswithEvent:(UIEvent*)event
-(void)touchesEnded:(NSSet*)toucheswithEvent:(UIEvent*)event
-(void)touchesCancelled:(NSSet*)toucheswithEvent:(UIEvent*)event
我們用的非常多,但是大家知道這4個方法是誰的執行個體方法嗎?如果你一下就說出是UIView的,那麼為什麼我們在UIViewController中也可以用呢,他們不是繼承關系。
注意這4個執行個體方法來自UIView與UIViewController的共同父類:UIResponder。它是我們今天的主角。
基本上我們所能看到的所有圖形界面都是繼承自UIResponder的,So,它究竟為何方神聖?
UIResponder所謂很多視圖的父類,他掌管着使用者的操作事件分發大權。如果沒有他,我們的電容屏如何将使用者的操作傳遞給我們的視圖令其做出反應呢?
我們先看看iOS中的響應者鍊的概念:
每一個應用有一個響應者鍊,我們的視圖結構是一個N叉樹(一個視圖可以有多個子視圖,一個子視圖同一時刻隻有一個父視圖),而每一個繼承UIResponder的對象都可以在這個N叉樹中扮演一個節點。當葉節點成為最高響應者的時候,從這個葉節點開始往其父節點開始追朔出一條鍊,那麼對于這一個葉節點來講,這一條鍊就是目前的響應者鍊。響應者鍊将系統捕獲到的UIEvent與UITouch從葉節點開始層層向下分發,期間可以選擇停止分發,也可以選擇繼續向下分發。
例子:
我用SingleView模闆建立了一個新的工程,它的主Window上隻有一個UIViewController,其View之上有一個Button。這個項目中所有UIResponder的子類所構成的N叉樹為這樣的結構:
那麼他看起來并不像N叉樹,但是不代表者不是一顆N叉樹,當我們項目複雜之後,這個View可不可以有多個UIButton節點?是以他就是一棵樹。
實際上我們要把這棵樹寫完整,應該還要算上UIButton的UILabel和UIImageView,因為他們也是UIReponder的子類。這裡先不考慮了。
我們對UIButton來講,他此時若是葉節點,那麼這時我們針對他所在的響應鍊來說,他在他之前的響應者就應該是我們controller的view(樹中的葉節點比父節點永遠更優先被分發事件,但是并不是說他就能在時間上先響應,我們下面講為什麼)。是以我們嘗試在任意地方列印這個Button的nextReponder對象。nextResponder對象是UIReponder類的執行個體方法,它會傳回任意對象在樹中的上一個響應者執行個體:
NSLog(@"%@",_testButton.nextResponder);
控制台輸出消息:
2013-09-2103:40:25.989響應鍊[614:60b]<UIView:0x16555e10;frame=(00;320568);autoresize=RM+BM;layer=<CALayer:0x16555e70>>
我們可以根據這個UIView的尺寸來得知,他就是我們唯一的控制器中的那個UIView。
接下來我們再列印下這個UIView的下一個響應者是誰:
NSLog(@"%@",_testButton.nextResponder.nextResponder);
輸出:
2013-09-2103:45:03.914響應鍊[621:60b]<RSViewController:0x15da0e30>
依次看,接着加一個nextResponder:
2013-09-2103:50:49.428響應鍊[669:60b](null)
為什麼這裡ViewController沒有父親呢?
注意這句代碼我是寫在ViewDidLoad中,而我們知道這個方法的生命周期比較早,是以我們換個地方寫或者延遲一段時間再列印,兩種方法都可以得到結果(由此可以推理出我們響應者樹的構造過程是在ViewDidLoad周期中來完成的,這個函數會将目前執行個體的構成的響應者子樹合并到我們整個根樹中):
2013-09-2103:53:47.304響應鍊[681:60b]<UIWindow:0x14e24200;frame=(00;320568);gestureRecognizers=<NSArray:0x14e242e0>;layer=<UIWindowLayer:0x14e244a0>>
再繼續往上追朔:
doubledelayInSeconds=2.0;
dispatch_time_tpopTime=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(delayInSeconds*NSEC_PER_SEC));
dispatch_after(popTime,dispatch_get_main_queue(),^(void){
NSLog(@"%@",_testButton.nextResponder.nextResponder.nextResponder.nextResponder);
});
2013-09-2103:56:22.043響應鍊[690:60b]<UIApplication:0x15659c00>
再加一個:
2013-09-2103:56:51.186響應鍊[696:60b]<RSAppDelegate:0x16663520>
那麼我們的appDelegate還有沒有父節點?
2013-09-2103:57:22.588響應鍊[706:60b](null)
沒有了,注意,一個從葉節點開始分發的事件,最多也就隻能分發到我們的AppDelegate了!
這個樹形結構在我們的項目中尤為重要,舉個栗子,如果我們想在一個view中重寫UITouchEvent的4個方法,并且不影響他的父視圖也響應這些事件,就要注意你重寫的方式了,比如我們在ViewController中重寫touchBegan如下:
NSLog(@"ViewController接收到觸摸事件");
在appDelegate的中同樣也寫上這一段:
NSLog(@"appDelegate接收到觸摸事件");
那麼究竟是誰被觸發呢?
2013-09-2104:02:49.405響應鍊[743:60b]ViewController接收到觸摸事件
這個很好了解,我剛剛也說了,viewController明顯是appDelegate的子節點,他有事件分發的優先權。如果我們想兩個地方都觸發呢?這裡super一下就可以了:
[supertouchesBegan:toucheswithEvent:event];
2013-09-2104:07:26.206響應鍊[749:60b]appDelegate接收到觸摸事件
2013-09-2104:07:26.208響應鍊[749:60b]ViewController接收到觸摸事件
注意看時間戳,appDelegate雖然優先級别不如ViewController,但是他響應的時間上面足足比ViewController早了0.002秒,我這裡試了幾次,都是相差0.002秒。
那麼我們分析一下這裡的響應者鍊是怎樣工作的:
使用者手指觸摸到了UIView上,由于我們沒有重寫UIView的UITouchEvent,是以他裡面和super執行的一樣的,将該事件繼續分發到UIViewController;
UIViewController的TouchBegan被我們重寫了,如果我們不super,那麼我們在這裡寫響應代碼。事件到這裡就不繼續分發了。可想而知,UIViewController祖先節點:UIWindow,UIApplication,AppDelegate都無權被分發此事件。
如果我們super了TouchBegan,那麼此次觸摸事件由
ViewController分發給UIWindow,
UIWindow繼而分發給UIApplication,
UIApplication再分發給AppDelegate,
于是我們在ViewController和appDelegate的touchBegan方法中都捕獲到了這次事件。
到這裡大家應該對這個響應者樹有一個很好的了解了吧?
接下來我們再談談第一響應者,和UIButton上的事件分發。