iOS文件補完計劃--UIResponder

UIResponder主要是負責響應我們螢幕上各種事件、並維護一個響應鏈的機制。
日常工作中我們主要用到:
響應鏈及其管理、第一響應者、響應觸控事件、驗證命令(Menu選單)、管理輸入檢視(自定義鍵盤)等等。
ofollow,noindex">目錄
- UIResponder
- 響應鏈
- 管理響應者鏈
- nextResponder
- isFirstResponder
- canBecomeFirstResponder
- becomeFirstResponder
- canBecomeFirstResponder
- resignFirstResponder
- 響應觸控事件
- touchesBegan:withEvent:
- touchesMoved:withEvent:
- touchesEnded:withEvent:
- touchesCancelled:withEvent:
- 響應鏈與手勢的恩怨糾葛
- touchesEstimatedPropertiesUpdated:
- touches引數
- UITouch
- UIEvent
- 響應動作事件
- motionBegan:withEvent:
- motionEnded:withEvent:
- motionCancelled:withEvent:
- 響應遠端控制事件
- remoteControlReceivedWithEvent:
- 驗證命令
- canPerformAction:withSender:
- targetForAction:withSender:
- 關於選單(UIMenuController)
- 撤消管理器
- undoManager
- 訪問快捷鍵命令
- keyCommands
- 管理輸入檢視
- inputView
- inputAccessoryView
- inputViewController
- inputAccessoryViewController
- reloadInputViews
- 管理文字輸入模式
- textInputMode
- textInputContextIdentifier
- clearTextInputContextIdentifier
- inputAssistantItem
- User Activities
- userActivity
- updateUserActivityState:
- restoreUserActivityState:
- UIEventSubtype
- UITouch
- UIEvent
- Touch Event、UIControl、UIGestureRecognizer三兄弟的恩怨情仇
UIResponder
在UIKit中,UIApplication、UIView、UIViewController這幾個類都是直接繼承自UIResponder類。另外SpriteKit中的SKNode也是繼承自UIResponder類。因此UIKit中的檢視、控制元件、檢視控制器,以及我們自定義的檢視及檢視控制器都有響應事件的能力。這些物件通常被稱為響應物件,或者是響應者(以下我們統一使用響應者)。
響應鏈
這個響應鏈指的的是處理點選事件的鏈條( 在此之前還要經歷由主視窗向上遍歷找到觸控點最終控制元件的過程 )。通常第一個響應者位於層級最上方、然後是其俯檢視以此類推、鏈條末端為 UIApplication
物件。
你可以參考這篇文章 《史上最詳細的iOS之事件的傳遞和響應機制-原理篇》 、 《iOS觸控事件全家桶》
文章很雜、所以簡單總結一下:
對於事件( 注意我沒說包括手勢和UIControl啊
)而言、分為兩個階段:
- 事件產生、尋找最合適的View
UIApplication
->UIWindow
->父View
->子view
- 找到最合適的View、進入響應鏈進行處理
可以參照下面對於nextResponder
的解釋。如果響應者鏈上有一個響應者可以處理該事件(實現了touch方法
)。則交由他處理、否則最後將被UIApplication
丟棄。
此外、如果控制元件的 userInteractionEnabled
為 NO
、是不會進入響應鏈中的(但是hidden和alpha不影響、即使看不見控制元件也可以使其成為第一響應者)。
管理響應者鏈
我們可以讓一個UIResponder物件參與到響應鏈中、以便讓他響應一些事件(比如手機搖動、彈起鍵盤)等等。
-
- nextResponder
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
返回響應者鏈中的下一個物件、如果沒有則返回nil。
UIResponder本身並不知道誰才是下一個響應者、所以如果想使用這個方法我們 必須過載其子類的get方法
。
對於 UIView及其子類
、預設的實現是這樣:
- 如果是控制器的
View
(也就是通常的self.view
)、返回控制器。 - 反之、返回其父檢視(
superview
)。 - 如果都不滿足、返回nil。
利用這個特性、我們可以查詢UIView的根控制器
UIView * view= [UIView new]; [self.view addSubview:view]; UIViewController * controller; id next = [view nextResponder]; while(![next isKindOfClass:[ViewController class]] && next) { NSLog(@"%@",next); next = [next nextResponder]; } if ([next isKindOfClass:[ViewController class]]) { controller = (ViewController *)next; }
還可以將事件一級一級往下傳
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 這裡可以做子view自己想做的事,做完後,事件繼續上傳,就可以讓其父類,甚至父viewcontroller獲取到這個事件了 [[self nextResponder] touchesBegan:touches withEvent:event]; }
對於 UIViewController
:
會返回 vc.view.superview
、如果沒有則為nil。
UIViewController * vc = [UIViewController new]; [self.view addSubview:vc.view]; NSLog(@"\nself.view--%@\nself.view.superview--%@\n[self nextResponder]--%@\nvc.view.superview--%@\n[vc nextResponder]--%@",self.view,self.view.superview,[self nextResponder],vc.view.superview,[vc nextResponder]); //列印結果: self.view--<UIView: 0x7fbe9841b3b0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60000002c360>> self.view.superview--(null) [self nextResponder]--(null) vc.view.superview--<UIView: 0x7fbe9841b3b0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60000002c360>> [vc nextResponder]--<UIView: 0x7fbe9841b3b0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60000002c360>>
對於 UIWindow
:
返回 UIApplication
物件
對於 UIApplication
:
返回nil。也就是丟棄該事件的響應機會。
-
- isFirstResponder
@property(nonatomic, readonly) BOOL isFirstResponder;
判定是否為第一響應者
我個人只知道對 UITextField
有用。其他的還望補充。
-
- canBecomeFirstResponder
@property(nonatomic, readonly) BOOL canBecomeFirstResponder;
返回檢視能否被作為第一響應者
預設(以及UIView/UIButton等)為NO、有鍵盤的控制元件(
)為YES。
canBecomeFirstResponder
返回YES是讓物件能夠呼叫 becomeFirstResponder
的先決條件。畢竟很多控制元件只希望處理與自己有關的事件而生(比如UIButton)。
-
- becomeFirstResponder
- (BOOL)becomeFirstResponder;
嘗試讓物件稱為第一響應者。如果他當前 已經是
則返回YES、反之為NO。
就想之前所說、稱為第一響應者並不代表能夠攔截上層的點選事件。只是對於一些特殊事件(鍵盤、搖動等等)具有優先響應權。
-
- canBecomeFirstResponder
@property(nonatomic, readonly) BOOL canResignFirstResponder;
返回檢視能否放棄第一響應者
官方文件來看~預設都是YES。
你可以通過過載這個方法來讓鍵盤無法回收、知道使用者輸入了你合適的內容(
)。
-
- resignFirstResponder
- (BOOL)resignFirstResponder;
如果已經是第一響應者、呼叫會返回YES並且取消其第一響應者的位置。否則NO。
過載的效果同上、不允許其放棄響應許可權。
第一響應物件和其他響應物件之間有什麼區別?對於普通的觸控事件沒什麼區別。就算我把一個按鈕設定成第一響應物件,當我點選其他按鈕時,還是會響應其他按鈕,而不會優先響應第一響應物件。
第一響應物件的區別在於負責處理那些和螢幕位置無關的事件,例如搖動、鍵盤輸入、控制面板(
就是下滑出來的那個?
)。
蘋果官方文件的說法是:第一響應物件是視窗中,應用程式認為最適合處理事件的物件
1. 另外需要注意的是隻有當檢視是檢視層次結構的一部分時才呼叫(所有和 FirstResponder
有關的)方法、否則很有可能返回一個未知結果。
如果檢視的window屬性(這個我回頭要去看看文件)不為空時、檢視才在一個檢視層次結構中;如果該屬性為nil、則檢視不在任何層次結構中。
2. 隱藏控制元件並不能取消其第一響應者的許可權
隱藏UITextField、鍵盤不會自動落下。你需要手動 resignFirstResponder
才行。
響應觸控事件
負責處理 螢幕的觸控事件
這四個方法預設都是什麼都不做。不過、UIKit中UIResponder的子類、尤其是UIView、對於這幾個方法的實現都會把訊息傳遞到響應鏈上。
一旦響應鏈中有人過載了touch方法、則由其處理、不再傳遞。
因此、為了不阻斷響應鏈、我們的子類在重寫時需要呼叫父類的相應方法。而不要將訊息直接傳送給下一響應者( 除非你能夠確定該類只做了這個操作 )。
-
- touchesBegan:withEvent:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
一個或多個手指
在檢視或視窗上 觸控
-
- touchesMoved:withEvent:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
一個或多個手指
在檢視或視窗上 移動
-
- touchesEnded:withEvent:
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
一個或多個手指
從檢視或視窗上 抬起
-
- touchesCancelled:withEvent:
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
當觸控事件被系統事件 取消(打斷)
時
需要注意、這裡不只是打電話等等。手勢事件也可能會打斷響應鏈的傳遞。因為手勢比響應鏈擁有更高的優先順序、這也是為什麼添加了手勢的View會阻止子View響應鏈的原因。 《iOS觸控事件那點兒事》 / 《iOS點選事件和手勢衝突》
-
響應鏈與手勢的恩怨糾葛
簡而言之:
-
手勢觸控
的優先順序高於UIResponder
- 一開始會同時觸發
手勢判斷
+touchBegan
- 手勢判斷成功、
cancel
掉touch傳遞 - 手勢判斷失敗、繼續由響應鏈處理
- 除此之外。
UIControl
子類(如UIButton
)的事件由UIApplication
分發。並不會被父View的手勢阻礙。
-
- touchesEstimatedPropertiesUpdated:
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
3D Touch相關方法。包括有一些觸控屬性UITouchPropertyie、比如壓力大小等等。
-
touches引數
- 當用一根手指觸控式螢幕幕時
會建立一個與手指相關聯的UITouch物件。 - 當用兩根手指同時觸控式螢幕幕
則會呼叫一次touchesBegan方法、建立兩個UITouch物件。 - 當不是同時觸控
呼叫兩次方法,每次的touches引數都只有一個UITouch物件。
那麼、如何判斷雙指觸控?
- 同時觸控:
NSSet有多少個UITouch物件元素 - 先後觸控:
可以參考一下 《iOS 觸控事件之雙指先後觸控問題的解決》 。其一是對touchesBegan
方法進行計數、其二是直接註冊UIPanGestureRecognizer
的兩指事件。
-
UITouch
UITouch儲存著跟本次手指觸控相關的資訊
- 一個手指第一次點選屏、會形成一個UITouch物件、直到離開銷燬
當手指移動時、系統會更新同一個UITouch物件、使之能夠一直儲存該手指的觸控位置。(也就是說上面四個方法、很大概率還是同一個UITouch) - 當前手指觸碰的螢幕位置等資訊
觸控的位置、時間、次數等。 - UITouch物件的TouchPhase儲存當前狀態
包括開始觸碰、移動、保持、離開、被取消。
-
UIEvent
第一個手指開始觸控式螢幕幕到最後一個手指離開螢幕定義為一個觸控事件
- UIEvent實際可以包括多個UITouch物件
有幾個手指觸碰,就會有幾個UITouch物件。 - UITouch物件包括當前手指觸碰的螢幕位置等資訊
- 一次完整的觸控過程中、只會產生一個事件物件
4個觸控方法都是同一個event引數。
響應動作事件
- 負責處理
裝置的動作事件
、比如搖一搖。
引數motion
是一個UIEventSubtype
型別結構體。
就我目前的理解、只有UIEventSubtypeMotionShake
也就是搖晃事件會走到幾個方法裡。 - 和觸碰事件一樣、這幾個方法的預設操作也是什麼都不做。不過,UIKit中UIResponder的子類,尤其是UIView,這幾個方法的實現都會把訊息傳遞到響應鏈上。
- 想要響應裝置的動作事件、該物件必須為
第一響應者
-
- motionBegan:withEvent:
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
動作事件開始
-
- motionEnded:withEvent:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
動作事件結束
-
- motionCancelled:withEvent:
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
動作事件被系統事件打斷(參考touch事件)
響應遠端控制事件
-
- remoteControlReceivedWithEvent:
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);
遠端控制事件來源於一些外部的配件,如耳機等。使用者可以通過耳機來控制視訊或音訊的播放。
引數 event.subtype
是一個 UIEventSubtype
型別的列舉。我們可以根據該屬性來實現具體操作。
需要注意的是
想要響應遠端控制、你必須啟動遠端控制 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
相對的、如果不想響應了也需要關閉 [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
此外、有人說如果是UIViewController或者UIApplication、不必成為第一響應者也可以接收遠端事件。(
)
驗證命令
在工作中經常要處理需要選單 UIMenuController
命令、如複製貼上等。以下兩個方法為此服務。
-
- canPerformAction:withSender:
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(3_0);
返回這個類是否支援某種選單操作。
需要注意的是這個方法只能決定本物件是否支援、但具體結果會受整條響應鏈影響。
-
- targetForAction:withSender:
- (nullable id)targetForAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(7_0);
返回能夠響應 action
事件的物件
內部的預設實現是呼叫自身的 canPerformAction:withSender
方法、如果是YES則返回self。否則在響應鏈上繼續用 targetForAction
查詢。
這個方法中返回的物件、會負責點選按鈕的方法實現。
所以、我們如果想從根本上杜絕某個action。應該將第一響應物件的某個action在 targetForAction
中返回nil 。而不是在 canPerformAction
中返回NO(因為實際結果受到響應鏈其他獨享的影響)。
對於 action
引數、
這裡第一種是一定會執行的、也就是會把 UIResponderStandardEditActions
的協議方法都輪詢一遍
- UIResponderStandardEditActions 協議中定義的方法。
如果返回YES、Menu會自動為我們新增對應的按鈕。(當然、具體實現還是需要我們自己寫的)。 - 自定義aciton
UIMenuItem *copyItem = [[UIMenuItem alloc] initWithTitle:@"copy" action:@selector(copyAciton:)]; [[UIMenuController sharedMenuController] setMenuItems:@[copyItem]];
-
關於選單(
UIMenuController
)
- 我們可以在一個長安手勢方法中新增如下程式碼顯示選單
[[UIMenuController sharedMenuController] setTargetRect:self.frame inView:self.superview]; [[UIMenuController sharedMenuController] setMenuVisible:YES animated: YES];
-
Menu會去呼叫第一響應者的
targetForAction:withSender:
方法決定需要展示哪些按鈕。
-
點選按鈕之後執行會再次向第一響應者查詢
targetForAction:withSender:
決定該由哪個物件進行處理。 -
關於這裡為什麼沒說
canPerformAction:withSender:
以及響應鏈、請返回去看targetForAction:withSender:
的內部實現。
撤消管理器
-
undoManager
@property(nonatomic, readonly) NSUndoManager *undoManager;
被用做撤消和反撤消功能
預設情況下,程式的每一個window都有一個undo管理器,它是一個用於管理undo和redo操作的共享物件。然而,響應鏈上的任何物件的類都可以有自定義undo管理器。例如,UITextField的例項的自定義管理器在檔案輸入框放棄第一響應者狀態時會被清理掉。當需要一個undo管理器時,請求會沿著響應鏈傳遞,然後UIWindow物件會返回一個可用的例項。
這裡有個挺簡單的例子、有興趣可以可以自己試試。 《GitHub》
訪問快捷鍵命令
-
keyCommands
@property (nullable,nonatomic,readonly) NSArray<UIKeyCommand *> *keyCommands NS_AVAILABLE_IOS(7_0); // returns an array of UIKeyCommand objects<
我們的應用可以支援外部裝置,包括外部鍵盤。在使用外部鍵盤時,使用快捷鍵可以大大提高我們的輸入效率。因此從iOS7後,UIResponder類新增了一個只讀屬性keyCommands,來定義一個響應者支援的快捷鍵。
我們用這個方法返回的快捷鍵命令陣列被用於整個響應鏈。當與快捷鍵命令物件匹配的快捷鍵被按下時,UIKit會沿著響應鏈查詢實現了響應行為方法的物件。它呼叫找到的第一個物件的方法並停止事件的處理。可以參閱: 《你真的瞭解UIResponder嗎?》 中相關的部分。
管理輸入檢視
所謂的輸入檢視、是指當物件為第一響應者時、顯示另外一個檢視用來處理當前物件的資訊輸入、如UITextView和UITextField兩個物件。
在其成為第一響應者時、會顯示一個系統鍵盤、用來輸入資訊。這個系統鍵盤就是輸入檢視。輸入檢視有兩種,一個是inputView,另一個是inputAccessoryView。這兩者如圖所示:

對於UITextField和UITextField以外的控制元件,inputView和inputAccessoryView是隻讀的、當然你可以繼承某個控制元件、然後過載這兩個屬性。
-
inputView
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputView NS_AVAILABLE_IOS(3_2);
當接收者成為第一個響應者時顯示的自定義輸入檢視
-
inputAccessoryView
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputAccessoryView NS_AVAILABLE_IOS(3_2);
當接收器成為第一響應者時顯示的自定義輸入附件檢視
-
inputViewController
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputViewController NS_AVAILABLE_IOS(8_0);
自定義輸入檢視控制器在接收器成為第一響應者時使用
-
inputAccessoryViewController
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputAccessoryViewController NS_AVAILABLE_IOS(8_0);
自定義輸入附件檢視控制器,用於在接收器成為第一響應者時顯示
-
reloadInputViews
- (void)reloadInputViews NS_AVAILABLE_IOS(3_2);
更新其自定義輸入和附件檢視(只對第一響應者起作用)
用處的話、我只找到一個切換鍵盤、歡迎大佬補充。
if (seg.selectedSegmentIndex == 1) { self.xfg_keyboard = [[XFG_KeyBoard alloc] initWithNumber:@2]; self.textField.inputView = self.xfg_keyboard; self.xfg_keyboard.delegate = self; [self.textField reloadInputViews]; } if (seg.selectedSegmentIndex == 2) { self.xfg_keyboard = [[XFG_KeyBoard alloc] initWithNumber:@3]; self.textField.inputView = self.xfg_keyboard; self.xfg_keyboard.delegate = self; [self.textField reloadInputViews]; }
- 輸入控制器和輸入檢視的區別
輸入控制器 包含
一個輸入檢視、並且由很多方法提供鍵盤支援。相對的、輸入檢視則只是一個普通的View。可能你需要用代理的方式將點選事件進行傳遞。 《iOS8新特性擴充套件(Extension)應用之四——自定義鍵盤控制元件》
通過以上的幾個屬性、你可以嘗試(我自己沒試、因為沒這些需求~)
自定義鍵盤: 《深入講解iOS鍵盤三:自定義鍵盤的兩種方法》 。
讓任意控制元件彈起鍵盤: 《iOS開發inputView和inputAccessoryView》
更好的支援展示字串型別的表情 《談UITextView、UITextField的InPutView和AccessoryInputView的便利》
管理文字輸入模式
鍵盤次序可以看做一個佇列,UIKit 有公共的鍵盤次序。但我們可以針對UIResponder子類進行一些特化
-
textInputMode
@property (nullable, nonatomic, readonly, strong) UITextInputMode *textInputMode NS_AVAILABLE_IOS(7_0);
指定 UIResponder 調起鍵盤時候顯示的鍵盤型別。 忽略公共的鍵盤次序 。
注意這裡是調起鍵盤時的樣式、而不是永久的樣式。
//無視系統順序、修改調起鍵盤樣式 - (UITextInputMode *)textInputMode { static UITextInputMode * emojiMode; if (emojiMode) { return emojiMode; } for (UITextInputMode * inputModel in [UITextInputMode activeInputModes]) { NSLog(@"%@",inputModel.primaryLanguage); if ([inputModel.primaryLanguage isEqualToString:@"emoji"]) { emojiMode = inputModel; } } return emojiMode; } //列印 2018-09-03 10:26:33.727471+0800 NSObject[20135:1592579] zh-Hans 2018-09-03 10:26:33.727626+0800 NSObject[20135:1592579] en-US 2018-09-03 10:26:33.727733+0800 NSObject[20135:1592579] emoji
-
textInputContextIdentifier
@property (nullable, nonatomic, readonly, strong) NSString *textInputContextIdentifier NS_AVAILABLE_IOS(7_0);
這個屬性,只要它不為空,那麼相當於開啟了對本responder的textInputMode的實時儲存。每次在該responder變成第一responder的時候,彈出的鍵盤都會是上一次選擇的那種鍵盤。
。
-
+ clearTextInputContextIdentifier:
+ (void)clearTextInputContextIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(7_0);
呼叫這個方法可以從程式的user default中移除與指定標識相關的所有文字輸入模式。移除這些資訊會讓響應者重新使用預設的文字輸入模式。
-
inputAssistantItem
- (void)reloadInputViews NS_AVAILABLE_IOS(3_2);
iPad鍵盤上方的快捷方式欄。iPhone或iPod Touch上沒有快捷方式欄
包含用於管理文字的輸入建議和其他控制元件,例如剪下,複製和貼上命令
User Activities
從iOS 8起,蘋果為我們提供了一個非常棒的功能,即 Handoff
。使用這一功能,我們可以在一部iOS裝置的某個應用上開始做一件事,然後在另一臺iOS裝置上繼續做這件事(比如在Mac上覆制、在iPhone上貼上)。 Handoff
的基本思想是使用者在一個應用裡所做的任何操作都可以看作是一個 Activity
,一個 Activity
可以和一個特定 iCloud
使用者的多臺裝置關聯起來。在編寫一個支援 Handoff
的應用時,會有以下三個互動事件:
User Activity User Activity User Activity
為了支援這些互動事件,在iOS 8後, UIResponder
類新增了幾個方法,我們在此不討論這幾個方法的實際使用,想了解更多的話,可以參考 iOS 8 Handoff 開發指南 。我們在此只是簡單描述一下這幾個方法。
-
userActivity
@property (nullable, nonatomic, strong) NSUserActivity *userActivity NS_AVAILABLE_IOS(8_0);
由UIKit管理的User Activities
會在適當的時間自動儲存。一般情況下,我們可以重寫UIResponder類的updateUserActivityState:方法來延遲新增表示User Activity的狀態資料。當我們不再需要一個User Activity時,我們可以設定userActivity屬性為nil。任何由UIKit管理的NSUserActivity物件,如果它沒有相關的響應者,則會自動失效。
-
- updateUserActivityState:
- (void)updateUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0);
更新使用者的活動。
你也可以理解成傳送、這個方法會由系統自動呼叫。
你可以向這個avtivity新增資料然後呼叫super進行傳送。
-
- restoreUserActivityState:
- (void)restoreUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0);
恢復使用者的操作
你可以在這裡獲取到 userActivity
用於恢復使用者操作。
這個方法需要被子類重寫、並且由AppDelegate呼叫
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler { UINavigationController *navi = (UINavigationController *)self.window.rootViewController; [navi.topViewController restoreUserActivityState:userActivity]; return YES; }
對於iOS9、NSUserActivity還有另一個用途、 Search API
。就是類似在主螢幕搜尋微博內容的功能。
或許你可以參考一下 《iOS Search API - NSUserActivity》
UITouch
//觸控事件在螢幕上有一個週期 typedef NS_ENUM(NSInteger, UITouchPhase) { UITouchPhaseBegan,//開始觸控 UITouchPhaseMoved,//移動 UITouchPhaseStationary,//停留 UITouchPhaseEnded,//觸控結束 UITouchPhaseCancelled,//觸控中斷 }; //檢測是否支援3DTouch typedef NS_ENUM(NSInteger, UIForceTouchCapability) { UIForceTouchCapabilityUnknown = 0,//3D Touch檢測失敗 UIForceTouchCapabilityUnavailable = 1,//3D Touch不可用 UIForceTouchCapabilityAvailable = 2//3D Touch可用 }; NS_CLASS_AVAILABLE_IOS(2_0) @interface UITouch : NSObject //觸控產生或變化的時間戳 只讀 @property(nonatomic,readonly) NSTimeIntervaltimestamp; //觸控週期內的各個狀態 @property(nonatomic,readonly) UITouchPhasephase; //短時間內點選的次數 只讀 @property(nonatomic,readonly) NSUIntegertapCount; //獲取手指與螢幕的接觸半徑 IOS8以後可用 只讀 @property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0); //獲取手指與螢幕的接觸半徑的誤差 IOS8以後可用 只讀 @property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0); //觸控時所在的視窗 只讀 @property(nullable,nonatomic,readonly,strong) UIWindow*window; //觸控時所在檢視 @property(nullable,nonatomic,readonly,strong) UIView*view; //獲取觸控手勢 @property(nullable,nonatomic,readonly,copy)NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2); //取得在指定檢視的位置 // 返回值表示觸控在view上的位置 // 這裡返回的位置是針對view的座標系的(以view的左上角為原點(0,0)) // 呼叫時傳入的view引數為nil的話,返回的是觸控點在UIWindow的位置 - (CGPoint)locationInView:(nullable UIView *)view; //該方法記錄了前一個觸控點的位置 - (CGPoint)previousLocationInView:(nullable UIView *)view; //獲取觸控壓力值,一般的壓力感應值為1.0 IOS9 只讀 @property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0); //獲取最大觸控壓力值 @property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0); @end
需要熟記的就是兩個獲取UITouch事件相對於UIView的方法。
舉一個拖拽的例子:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ // 想讓控制元件隨著手指移動而移動,監聽手指移動 // 獲取UITouch物件 UITouch *touch = [touches anyObject]; // 獲取當前點的位置 CGPoint curP = [touch locationInView:self]; // 獲取上一個點的位置 CGPoint preP = [touch previousLocationInView:self]; // 獲取它們x軸的偏移量,每次都是相對上一次 CGFloat offsetX = curP.x - preP.x; // 獲取y軸的偏移量 CGFloat offsetY = curP.y - preP.y; // 修改控制元件的形變或者frame,center,就可以控制控制元件的位置 // 形變也是相對上一次形變(平移) // CGAffineTransformMakeTranslation:會把之前形變給清空,重新開始設定形變引數 // make:相對於最原始的位置形變 // CGAffineTransform t:相對這個t的形變的基礎上再去形變 // 如果相對哪個形變再次形變,就傳入它的形變 self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY); }
UIEvent
//事件型別 typedef NS_ENUM(NSInteger, UIEventType) { UIEventTypeTouches,//觸控事件(通過觸控、手勢進行觸發,例如手指點選、縮放) UIEventTypeMotion,//運動事件,通過加速器進行觸發(例如手機晃動) UIEventTypeRemoteControl,//遠端控制事件通過其他遠端裝置觸發(例如耳機控制按鈕) }; // 觸控事件的型別 typedef NS_ENUM(NSInteger, UIEventSubtype) { UIEventSubtypeNone= 0, //搖晃 UIEventSubtypeMotionShake= 1, //播放 UIEventSubtypeRemoteControlPlay= 100, //暫停 UIEventSubtypeRemoteControlPause= 101, //停止 UIEventSubtypeRemoteControlStop= 102, //播放和暫停切換 UIEventSubtypeRemoteControlTogglePlayPause= 103, //下一首 UIEventSubtypeRemoteControlNextTrack= 104, //上一首 UIEventSubtypeRemoteControlPreviousTrack= 105, //開始後退 UIEventSubtypeRemoteControlBeginSeekingBackward = 106, //結束後退 UIEventSubtypeRemoteControlEndSeekingBackward= 107, //開始快進 UIEventSubtypeRemoteControlBeginSeekingForward= 108, //結束快進 UIEventSubtypeRemoteControlEndSeekingForward= 109, }; NS_CLASS_AVAILABLE_IOS(2_0) @interface UIEvent : NSObject //事件型別 @property(nonatomic,readonly) UIEventTypetype NS_AVAILABLE_IOS(3_0); // 觸控事件的型別 @property(nonatomic,readonly) UIEventSubtypesubtype NS_AVAILABLE_IOS(3_0); //事件的時間戳 @property(nonatomic,readonly) NSTimeIntervaltimestamp; //所有的觸控 - (nullable NSSet <UITouch *> *)allTouches; //獲得UIWindow的觸控 - (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window; //獲得UIView的觸控 - (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view; //獲得事件中特定手勢的觸控 - (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture NS_AVAILABLE_IOS(3_2); //會將丟失的觸控放到一個新的 UIEvent 陣列中,你可以用 coalescedTouchesForTouch(_:) 方法來訪問 - (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0); //輔助UITouch的觸控,預測發生了一系列主要的觸控事件。這些預測可能不完全匹配的觸控的真正的行為,因為它的移動,所以他們應該被解釋為一個估計。 - (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0); @end
Touch Event、UIControl、UIGestureRecognizer三兄弟的恩怨情仇
最後
本文主要是自己的學習與總結。如果文記憶體在紕漏、萬望留言斧正。如果願意補充以及不吝賜教小弟會更加感激。