在ios中,事件UIEvent類來表示,當一個事件發生時,系統會搜集的相關事件信息,創建一個UIEvent對象,最后將該事件轉發給應用程序對象(UIApplication)。日常生活中,主要有三種類型的事件:觸摸事件,加速計事件以及遠程遙控事件。下面是官方的一張圖片:
當用戶通過以上方式觸發一個事件時,會將相應的事件對象添加到UIApplication的事件隊列中。UIApplication會循環的從隊列中拿出第一個事件來處理。首先將該事件分發給UIApplication 的主窗口對象(KeyWindow),然后由主窗口決定如何將事件交給最合適的響應者(UIResponder)來處理取決於事件的類型。這里主要分兩種情況:
1、觸摸事件:UIApplication通過一個觸摸檢測來決定最合適來處理該事件的響應者,一般情況下,這個響應者是UIView對象。
2、加速計事件或遠程遙控事件:UIApplication尋找UIWindow中的第一響應者。找到第一響應者(The First Responder)后,會將該事件對象派發給該響應者以便處理。
下面分別討論上述兩種情況。
一、觸摸事件中的觸摸檢測
首先我們需要明確一個UIView對象能夠接收觸摸事件至少要保證以下三個條件:
1、userInteractionEnabled屬性為YES,該屬性表示允許控件同用戶交互。
2、Hidden屬性為NO。控件都看不見,還觸摸啥?
3、opacity屬性值0 ~0.01,不能透明過分了吧?
接下來的我們僅僅認為該三個基本屬性都滿足要求,方便描述,當然對於不滿足要求的自然是不能接收觸摸說事件的。
當用戶手指觸摸到屏幕中的某一塊區域時,UIWindow查找其子控件,然后通過調用所有自控件的方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
來通過指定的觸摸點獲取最合適的UIView來處理該觸摸事件。如何通過觸摸點獲取UIView原理其實非常簡單,只需要檢查該觸摸點是否在該控件所在的矩形區域內就可以了,其實hitTest:withEvent方法內部也是調用方法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
如果檢測到傳入的控件包含該觸摸點就返回YES。
當通過hitTest方法檢測獲取到UIView后,會繼續對該UIView對象做一次檢測操作,也就是查找subViews的subViews做觸摸檢測。最終該方法會返回一個最合適的控件來響應該事件。再次申明,如果之前的三個條件不滿足,那么該UIView以及其subViews都不可以響應該觸摸事件。
找到響應者后,響應者可以重寫以下方法來對觸摸事件做響應:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event];//讓下一個響應者可以有機會繼續處理 } - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; } - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; } - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; }
在響應方法內部,我們也可以將這個觸摸事件繼續傳遞給父控件的對應方法處理。然后父控件還可以將該事件繼續向上傳遞,直到傳遞給UIApplication對象。這一系列的響應者對象就構成了一個響應者鏈條。
二、第一響應者 (The First Responder)
什么是第一響應者?簡單的講,第一響應者是一個UIWindow對象接收到一個事件后,第一個來響應的該事件的對象。注意:這個第一響應者與之前討論的觸摸檢測到的第一個響應的UIView並不是一個概念。第一響應者一般情況下用於處理非觸摸事件(手機搖晃、耳機線控的遠程空間)或非本窗口的觸摸事件(鍵盤觸摸事件),通俗點講其實就是管別人閑事的響應者。在IOS中,當然管閑事並不是所有控件都願意的,這么說好像並不是很好理解,或着是站在編程人員的角度來看待這個問題,程序員負責告訴系統哪個對象可以成為第一響應者(canBecomeFirstResponder),如果方法canBecomeFirstResponder返回YES,這個響應者對象才有資格稱為第一響應者。有資格並不代表一定可以成為第一響應者,就好像符合要求並不一定能夠應聘成功一樣,所以還差一個聘用環節,那就是becomeFirstResponder正式成為第一響應者。
請原諒我的這些可能不太正常的想法,個人感覺上面的過程又有點像招聘流程,簡歷篩選就是canBecomeFirstResponder,becomeFirstResponder就是正式成為公司的員工。那么既然公司由聘用,那么對應的就有辭退咯!對應的方法就是canResignFirstResponder,這個表示第一響應者是否可以被辭退,有些牛逼到逆天的員工並不是說辭退就辭退的,爭取有一天可以成為這個逆天員工,好吧,我又扯遠了。還有一個方法就是resignFirstResponder,正式辭退該員工。
值得注意的是,一個UIWindow對象在某一時刻只能有一個響應者對象可以成為第一響應者。我們可以通過isFirstResponder來判斷某一個對象是否為第一響應者。
大家先看下面的一個手機界面:
界面中包含兩個輸入框,一個切換第一響應者的按鈕。我為兩個輸入框綁定了開始編輯事件,然后在事件中打印第一響應者相關的信息,代碼如下:
NSString * NSStringFromBoolValue(BOOL boolValue){ return boolValue ? @"YES" : @"NO"; } - (IBAction)editingBegin:(id)sender { NSLog(@"top : 是否可以成為第一響應者=>%@,是否第一響應者=>%@",NSStringFromBoolValue(self.topInputView.canBecomeFirstResponder),NSStringFromBoolValue(self.topInputView.isFirstResponder)); NSLog(@"down : 是否可以成為第一響應者=>%@,是否第一響應者=>%@",NSStringFromBoolValue(self.downInputView.canBecomeFirstResponder),NSStringFromBoolValue(self.downInputView.isFirstResponder)); }
當點擊第一個輸入框時,打印如下:
我們可以看到兩個輸入框都可以成為第一響應者。但是只有第一個輸入框才是第一響應者。
當點擊第二個輸入框時,打印如下:
我們可以看到兩個輸入框都是第一響應者,但是只有下面那個輸入框才是第一響應者。
我們注意到,兩個輸入框的下方有一個按鈕用於切換第一響應者。按鈕的響應事件方法為:
- (IBAction)switch:(id)sender { /** * 1、如果頂部輸入框是第一響應者就將第一響應者切換為下方的輸入框 2、如果頂部輸入框不是第一響應者,就將其設置為第一響應者。 */ if(self.topInputView.isFirstResponder){ [self.downInputView becomeFirstResponder]; }else{ [self.topInputView becomeFirstResponder]; } NSLog(@"父控件中的第一響應者:%@",[self.uiview findFirstResponder]); NSLog(@"top : 是否可以成為第一響應者=>%@,是否第一響應者=>%@",NSStringFromBoolValue(self.topInputView.canBecomeFirstResponder),
NSStringFromBoolValue(self.topInputView.isFirstResponder)); NSLog(@"down : 是否可以成為第一響應者=>%@,是否第一響應者=>%@",NSStringFromBoolValue(self.downInputView.canBecomeFirstResponder),
NSStringFromBoolValue(self.downInputView.isFirstResponder)); }
在點擊了第一個輸入框后,我們點擊切換第一響應者,屏幕打印如下:
我們可以看到,這個時候下方的輸入框成為第一響應者,並且觸發了開始編輯事件,所以有兩次打印。切換后,焦點也切換到第二個輸入框中,我們通過鍵盤輸入時,內容會在第二個輸入框中出現。
時常有人碰到希望點擊空白區域,隱藏鍵盤的問題。例如輸入框輸入一半,覺得不想再編輯了,可以點擊空白區域來隱藏鍵盤,這個時候其實只需要告訴系統,這個第一響應者的位置我不想要了,我想辭職,這就夠了!下面是點擊第一個輸入框后,然后點擊空白區域,觸發touchesBegin事件,最后topInputView辭職的過程,通過這種方式就可以隱藏鍵盤了。代碼如下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self.topInputView resignFirstResponder]; }
今天就先寫到這里,后續會補充一些細節性的東西。