iOS文檔補(bǔ)完計劃--UIResponder

UIResponder主要是負(fù)責(zé)響應(yīng)我們屏幕上各種事件蜈出、并維護(hù)一個響應(yīng)鏈的機(jī)制。
日常工作中我們主要用到:
響應(yīng)鏈及其管理涛酗、第一響應(yīng)者铡原、響應(yīng)觸摸事件、驗證命令(Menu菜單)商叹、管理輸入視圖(自定義鍵盤)等等燕刻。


目錄

  • UIResponder
  • 響應(yīng)鏈
  • 管理響應(yīng)者鏈
    • nextResponder
    • isFirstResponder
    • canBecomeFirstResponder
    • becomeFirstResponder
    • canBecomeFirstResponder
    • resignFirstResponder
  • 響應(yīng)觸摸事件
    • touchesBegan:withEvent:
    • touchesMoved:withEvent:
    • touchesEnded:withEvent:
    • touchesCancelled:withEvent:
    • touchesEstimatedPropertiesUpdated:
    • touches參數(shù)
    • UITouch
    • UIEvent
  • 響應(yīng)動作事件
    • motionBegan:withEvent:
    • motionEnded:withEvent:
    • motionCancelled:withEvent:
  • 響應(yīng)遠(yuǎn)程控制事件
    • remoteControlReceivedWithEvent:
  • 驗證命令
    • canPerformAction:withSender:
    • targetForAction:withSender:
    • 關(guān)于菜單(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中的視圖酪夷、控件、視圖控制器孽惰,以及我們自定義的視圖及視圖控制器都有響應(yīng)事件的能力晚岭。這些對象通常被稱為響應(yīng)對象,或者是響應(yīng)者(以下我們統(tǒng)一使用響應(yīng)者)勋功。


響應(yīng)鏈

這個響應(yīng)鏈指的的是處理點擊事件的鏈條(在此之前還要經(jīng)歷由主窗口向上遍歷找到觸摸點最終控件的過程)坦报。通常第一個響應(yīng)者位于層級最上方库说、然后是其俯視圖以此類推、鏈條末端為UIApplication對象片择。

你可以參考這篇文章《史上最詳細(xì)的iOS之事件的傳遞和響應(yīng)機(jī)制-原理篇》潜的、《iOS觸摸事件全家桶》

文章很雜、所以簡單總結(jié)一下:
對于事件(注意我沒說包括手勢和UIControl啊這兩個我還得研究研究)而言字管、分為兩個階段:

  1. 事件產(chǎn)生啰挪、自下而上尋找最合適的View
    UIApplication -> UIWindow -> 父View -> 子view
  2. 找到最合適的View、進(jìn)入響應(yīng)鏈進(jìn)行自上而下處理
    可以參照下面對于nextResponder的解釋嘲叔。如果響應(yīng)者鏈上有一個響應(yīng)者可以處理該事件(實現(xiàn)了touch方法)亡呵。則交由他處理、否則最后將被UIApplication丟棄硫戈。

此外锰什、如果控件的userInteractionEnabledNO、是不會進(jìn)入響應(yīng)鏈中的(但是hidden和alpha不影響丁逝、即使看不見控件也可以使其成為第一響應(yīng)者)汁胆。


管理響應(yīng)者鏈

我們可以讓一個UIResponder對象參與到響應(yīng)鏈中、以便讓他響應(yīng)一些事件(比如手機(jī)搖動果港、彈起鍵盤)等等沦泌。

  • - nextResponder
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;

返回響應(yīng)者鏈中的下一個對象、如果沒有則返回nil辛掠。

UIResponder本身并不知道誰才是下一個響應(yīng)者谢谦、所以如果想使用這個方法我們必須重載其子類的get方法

對于UIView及其子類萝衩、默認(rèn)的實現(xiàn)是這樣:

  1. 如果是控制器的View(也就是通常的self.view)回挽、返回控制器。
  2. 反之猩谊、返回其父視圖(superview)千劈。
  3. 如果都不滿足、返回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自己想做的事,做完后暗甥,事件繼續(xù)上傳喜滨,就可以讓其父類,甚至父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]);

//打印結(jié)果:
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。也就是丟棄該事件的響應(yīng)機(jī)會。

  • - isFirstResponder
@property(nonatomic, readonly) BOOL isFirstResponder;

判定是否為第一響應(yīng)者

我個人只知道對UITextField有用辜膝。其他的還望補(bǔ)充无牵。

  • - canBecomeFirstResponder
@property(nonatomic, readonly) BOOL canBecomeFirstResponder;

返回視圖能否被作為第一響應(yīng)者

默認(rèn)(以及UIView/UIButton等)為NO、有鍵盤的控件(不排除還有其他的)為YES厂抖。

canBecomeFirstResponder返回YES是讓對象能夠調(diào)用becomeFirstResponder的先決條件茎毁。畢竟很多控件只希望處理與自己有關(guān)的事件而生(比如UIButton)。

  • - becomeFirstResponder
- (BOOL)becomeFirstResponder;

嘗試讓對象稱為第一響應(yīng)者验游。如果他當(dāng)前已經(jīng)是則返回YES充岛、反之為NO。

就想之前所說耕蝉、稱為第一響應(yīng)者并不代表能夠攔截上層的點擊事件崔梗。只是對于一些特殊事件(鍵盤、搖動等等)具有優(yōu)先響應(yīng)權(quán)垒在。

  • - canBecomeFirstResponder
@property(nonatomic, readonly) BOOL canResignFirstResponder;  

返回視圖能否放棄第一響應(yīng)者

官方文檔來看~默認(rèn)都是YES蒜魄。

你可以通過重載這個方法來讓鍵盤無法回收、知道用戶輸入了你合適的內(nèi)容(叫爸爸??)场躯。

  • - resignFirstResponder
- (BOOL)resignFirstResponder;

如果已經(jīng)是第一響應(yīng)者谈为、調(diào)用會返回YES并且取消其第一響應(yīng)者的位置。否則NO踢关。

重載的效果同上伞鲫、不允許其放棄響應(yīng)權(quán)限。

第一響應(yīng)對象和其他響應(yīng)對象之間有什么區(qū)別签舞?對于普通的觸摸事件沒什么區(qū)別秕脓。就算我把一個按鈕設(shè)置成第一響應(yīng)對象,當(dāng)我點擊其他按鈕時儒搭,還是會響應(yīng)其他按鈕吠架,而不會優(yōu)先響應(yīng)第一響應(yīng)對象。
第一響應(yīng)對象的區(qū)別在于負(fù)責(zé)處理那些和屏幕位置無關(guān)的事件搂鲫,例如搖動傍药、鍵盤輸入、控制面板(就是下滑出來的那個?)魂仍。
蘋果官方文檔的說法是:第一響應(yīng)對象是窗口中拐辽,應(yīng)用程序認(rèn)為最適合處理事件的對象

1. 另外需要注意的是只有當(dāng)視圖是視圖層次結(jié)構(gòu)的一部分時才調(diào)用(所有和FirstResponder有關(guān)的)方法、否則很有可能返回一個未知結(jié)果擦酌。
如果視圖的window屬性(這個我回頭要去看看文檔)不為空時薛训、視圖才在一個視圖層次結(jié)構(gòu)中;如果該屬性為nil仑氛、則視圖不在任何層次結(jié)構(gòu)中。

2. 隱藏控件并不能取消其第一響應(yīng)者的權(quán)限
隱藏UITextField、鍵盤不會自動落下锯岖。你需要手動resignFirstResponder才行介袜。


響應(yīng)觸摸事件

負(fù)責(zé)處理屏幕的觸摸事件

這四個方法默認(rèn)都是什么都不做。
不過出吹、UIKit中UIResponder的子類遇伞、比如UIView、對于這幾個方法的實現(xiàn)都會把消息傳遞到響應(yīng)鏈上捶牢。
而對于UIControl鸠珠、內(nèi)部處理會直接影響到TouchDown以及TouchUpInside等狀態(tài)以及相關(guān)方法的觸發(fā)、并且不再向下傳遞秋麸。所以一定要調(diào)用super渐排、以保證Target-Action的正確觸發(fā)

一旦響應(yīng)鏈中有人重載了touch方法灸蟆、則由其處理驯耻、不再傳遞。
因此炒考、為了不阻斷響應(yīng)鏈可缚、我們的子類在重寫時需要調(diào)用父類的相應(yīng)方法。而不要將消息直接發(fā)送給下一響應(yīng)者(除非你能夠確定該類只做了這個操作)斋枢。

  • - 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;

當(dāng)觸摸事件被系統(tǒng)事件取消(打斷)

需要注意帘靡、這里不只是打電話等等。手勢事件也可能會打斷響應(yīng)鏈的傳遞瓤帚。因為手勢比響應(yīng)鏈擁有更高的優(yōu)先級描姚、這也是為什么添加了手勢的View會阻止子View響應(yīng)鏈的原因。《iOS觸摸事件那點兒事》/《iOS點擊事件和手勢沖突》

  • - touchesEstimatedPropertiesUpdated:
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

3D Touch相關(guān)方法缘滥。包括有一些觸摸屬性UITouchPropertyie轰胁、比如壓力大小等等。

  • touches參數(shù)
  1. 當(dāng)用一根手指觸摸屏幕時
    會創(chuàng)建一個與手指相關(guān)聯(lián)的UITouch對象朝扼。
  2. 當(dāng)用兩根手指同時觸摸屏幕
    則會調(diào)用一次touchesBegan方法赃阀、創(chuàng)建兩個UITouch對象。
  3. 當(dāng)不是同時觸摸
    調(diào)用兩次方法擎颖,每次的touches參數(shù)都只有一個UITouch對象榛斯。

那么、如何判斷雙指觸控搂捧?

  1. 同時觸摸:
    NSSet有多少個UITouch對象元素
  2. 先后觸摸:
    可以參考一下《iOS 觸摸事件之雙指先后觸摸問題的解決》驮俗。其一是對touchesBegan方法進(jìn)行計數(shù)、其二是直接注冊UIPanGestureRecognizer的兩指事件允跑。
  • UITouch

UITouch保存著跟本次手指觸摸相關(guān)的信息

  1. 一個手指第一次點擊屏王凑、會形成一個UITouch對象搪柑、直到離開銷毀
    當(dāng)手指移動時、系統(tǒng)會更新同一個UITouch對象索烹、使之能夠一直保存該手指的觸摸位置工碾。(也就是說上面四個方法、很大概率還是同一個UITouch)
  2. 當(dāng)前手指觸碰的屏幕位置等信息
    觸摸的位置百姓、時間渊额、次數(shù)等。
  3. UITouch對象的TouchPhase保存當(dāng)前狀態(tài)
    包括開始觸碰垒拢、移動旬迹、保持、離開求类、被取消奔垦。
  • UIEvent

第一個手指開始觸摸屏幕到最后一個手指離開屏幕定義為一個觸摸事件

  1. UIEvent實際可以包括多個UITouch對象
    有幾個手指觸碰,就會有幾個UITouch對象仑嗅。
  2. UITouch對象包括當(dāng)前手指觸碰的屏幕位置等信息
  3. 一次完整的觸摸過程中宴倍、只會產(chǎn)生一個事件對象
    4個觸摸方法都是同一個event參數(shù)。

響應(yīng)動作事件

  1. 負(fù)責(zé)處理設(shè)備的動作事件仓技、比如搖一搖鸵贬。
    參數(shù)motion是一個UIEventSubtype類型結(jié)構(gòu)體。
    就我目前的理解脖捻、只有UIEventSubtypeMotionShake也就是搖晃事件會走到幾個方法里阔逼。
  2. 和觸碰事件一樣、這幾個方法的默認(rèn)操作也是什么都不做地沮。不過嗜浮,UIKit中UIResponder的子類,尤其是UIView摩疑,這幾個方法的實現(xiàn)都會把消息傳遞到響應(yīng)鏈上危融。
  3. 想要響應(yīng)設(shè)備的動作事件、該對象必須為第一響應(yīng)者
  • - 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);

動作事件結(jié)束

  • - motionCancelled:withEvent:
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

動作事件被系統(tǒng)事件打斷(參考touch事件)


響應(yīng)遠(yuǎn)程控制事件

  • - remoteControlReceivedWithEvent:
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);

遠(yuǎn)程控制事件來源于一些外部的配件雷袋,如耳機(jī)等吉殃。用戶可以通過耳機(jī)來控制視頻或音頻的播放。

參數(shù)event.subtype是一個UIEventSubtype類型的枚舉楷怒。我們可以根據(jù)該屬性來實現(xiàn)具體操作蛋勺。

需要注意的是

想要響應(yīng)遠(yuǎn)程控制、你必須啟動遠(yuǎn)程控制[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
相對的鸠删、如果不想響應(yīng)了也需要關(guān)閉[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
此外抱完、有人說如果是UIViewController或者UIApplication、不必成為第一響應(yīng)者也可以接收遠(yuǎn)程事件刃泡。(這個我需要有空去真機(jī)上測一測)


驗證命令

在工作中經(jīng)常要處理需要菜單UIMenuController命令巧娱、如復(fù)制粘貼等碉怔。以下兩個方法為此服務(wù)。

  • - canPerformAction:withSender:
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(3_0);

返回這個類是否支持某種菜單操作禁添。

需要注意的是這個方法只能決定本對象是否支持眨层、但具體結(jié)果會受整條響應(yīng)鏈影響。

  • - targetForAction:withSender:
- (nullable id)targetForAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(7_0);

返回能夠響應(yīng)action事件的對象

內(nèi)部的默認(rèn)實現(xiàn)是調(diào)用自身的canPerformAction:withSender方法上荡、如果是YES則返回self。否則在響應(yīng)鏈上繼續(xù)用targetForAction查詢馒闷。
這個方法中返回的對象酪捡、會負(fù)責(zé)點擊按鈕的方法實現(xiàn)。

所以纳账、我們?nèi)绻霃母旧隙沤^某個action逛薇。應(yīng)該將第一響應(yīng)對象的某個action在targetForAction中返回nil。而不是在canPerformAction中返回NO(因為實際結(jié)果受到響應(yīng)鏈其他獨享的影響)疏虫。

對于action參數(shù)永罚、有兩種可能性:

這里第一種是一定會執(zhí)行的、也就是會把UIResponderStandardEditActions的協(xié)議方法都輪詢一遍

  1. UIResponderStandardEditActions協(xié)議中定義的方法卧秘。
    如果返回YES呢袱、Menu會自動為我們添加對應(yīng)的按鈕。(當(dāng)然翅敌、具體實現(xiàn)還是需要我們自己寫的)羞福。
  2. 自定義aciton
UIMenuItem *copyItem = [[UIMenuItem alloc] initWithTitle:@"copy" action:@selector(copyAciton:)];
[[UIMenuController sharedMenuController] setMenuItems:@[copyItem]];
  • 關(guān)于菜單(UIMenuController)
  1. 我們可以在一個長安手勢方法中添加如下代碼顯示菜單
[[UIMenuController sharedMenuController] setTargetRect:self.frame inView:self.superview];
[[UIMenuController sharedMenuController] setMenuVisible:YES animated: YES];
  1. Menu會去調(diào)用第一響應(yīng)者的targetForAction:withSender:方法
    決定需要展示哪些按鈕。

  2. 點擊按鈕之后執(zhí)行會再次向第一響應(yīng)者查詢targetForAction:withSender:
    決定該由哪個對象進(jìn)行處理蚯涮。

  3. 關(guān)于這里為什么沒說canPerformAction:withSender:以及響應(yīng)鏈治专、請返回去看targetForAction:withSender:的內(nèi)部實現(xiàn)。


撤消管理器

  • undoManager
@property(nonatomic, readonly) NSUndoManager *undoManager;

被用做撤消和反撤消功能

默認(rèn)情況下遭顶,程序的每一個window都有一個undo管理器张峰,它是一個用于管理undo和redo操作的共享對象。然而棒旗,響應(yīng)鏈上的任何對象的類都可以有自定義undo管理器喘批。例如,UITextField的實例的自定義管理器在文件輸入框放棄第一響應(yīng)者狀態(tài)時會被清理掉嗦哆。當(dāng)需要一個undo管理器時谤祖,請求會沿著響應(yīng)鏈傳遞,然后UIWindow對象會返回一個可用的實例老速。
這里有個挺簡單的例子粥喜、有興趣可以可以自己試試。《GitHub》


訪問快捷鍵命令

  • keyCommands
@property (nullable,nonatomic,readonly) NSArray<UIKeyCommand *> *keyCommands NS_AVAILABLE_IOS(7_0); // returns an array of UIKeyCommand objects<

我們的應(yīng)用可以支持外部設(shè)備橘券,包括外部鍵盤额湘。在使用外部鍵盤時卿吐,使用快捷鍵可以大大提高我們的輸入效率。因此從iOS7后锋华,UIResponder類新增了一個只讀屬性keyCommands嗡官,來定義一個響應(yīng)者支持的快捷鍵。

我們用這個方法返回的快捷鍵命令數(shù)組被用于整個響應(yīng)鏈毯焕。當(dāng)與快捷鍵命令對象匹配的快捷鍵被按下時衍腥,UIKit會沿著響應(yīng)鏈查找實現(xiàn)了響應(yīng)行為方法的對象。它調(diào)用找到的第一個對象的方法并停止事件的處理纳猫∑畔蹋可以參閱:《你真的了解UIResponder嗎?》中相關(guān)的部分芜辕。


管理輸入視圖

所謂的輸入視圖尚骄、是指當(dāng)對象為第一響應(yīng)者時、顯示另外一個視圖用來處理當(dāng)前對象的信息輸入侵续、如UITextView和UITextField兩個對象倔丈。

在其成為第一響應(yīng)者時、會顯示一個系統(tǒng)鍵盤状蜗、用來輸入信息需五。這個系統(tǒng)鍵盤就是輸入視圖。輸入視圖有兩種诗舰,一個是inputView警儒,另一個是inputAccessoryView。這兩者如圖所示:


對于UITextField和UITextField以外的控件眶根,inputView和inputAccessoryView是只讀的蜀铲、當(dāng)然你可以繼承某個控件、然后重載這兩個屬性属百。

  • inputView
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputView NS_AVAILABLE_IOS(3_2);

當(dāng)接收者成為第一個響應(yīng)者時顯示的自定義輸入視圖

  • inputAccessoryView
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputAccessoryView NS_AVAILABLE_IOS(3_2);

當(dāng)接收器成為第一響應(yīng)者時顯示的自定義輸入附件視圖

  • inputViewController
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputViewController NS_AVAILABLE_IOS(8_0);

自定義輸入視圖控制器在接收器成為第一響應(yīng)者時使用

  • inputAccessoryViewController
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputAccessoryViewController NS_AVAILABLE_IOS(8_0);

自定義輸入附件視圖控制器记劝,用于在接收器成為第一響應(yīng)者時顯示

  • reloadInputViews
- (void)reloadInputViews NS_AVAILABLE_IOS(3_2);

更新其自定義輸入和附件視圖(只對第一響應(yīng)者起作用)

用處的話、我只找到一個切換鍵盤族扰、歡迎大佬補(bǔ)充厌丑。

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];
}
- 輸入控制器和輸入視圖的區(qū)別

輸入控制器包含一個輸入視圖、并且由很多方法提供鍵盤支持渔呵。相對的怒竿、輸入視圖則只是一個普通的View±┣猓可能你需要用代理的方式將點擊事件進(jìn)行傳遞耕驰。《iOS8新特性擴(kuò)展(Extension)應(yīng)用之四——自定義鍵盤控件》

通過以上的幾個屬性、你可以嘗試(我自己沒試录豺、因為沒這些需求~)
自定義鍵盤:《深入講解iOS鍵盤三:自定義鍵盤的兩種方法》朦肘。
讓任意控件彈起鍵盤:《iOS開發(fā)inputView和inputAccessoryView》
更好的支持展示字符串類型的表情《談UITextView饭弓、UITextField的InPutView和AccessoryInputView的便利》


管理文本輸入模式

鍵盤次序可以看做一個隊列,UIKit 有公共的鍵盤次序媒抠。但我們可以針對UIResponder子類進(jìn)行一些特化

  • textInputMode
@property (nullable, nonatomic, readonly, strong) UITextInputMode *textInputMode NS_AVAILABLE_IOS(7_0);

指定 UIResponder 調(diào)起鍵盤時候顯示的鍵盤類型弟断。忽略公共的鍵盤次序

注意這里是調(diào)起鍵盤時的樣式趴生、而不是永久的樣式阀趴。

//無視系統(tǒng)順序、修改調(diào)起鍵盤樣式
- (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);

這個屬性苍匆,只要它不為空舍咖,那么相當(dāng)于開啟了對本responder的textInputMode的實時保存。每次在該responder變成第一responder的時候锉桑,彈出的鍵盤都會是上一次選擇的那種鍵盤。

存疑窍株、試了試好像不重載也會被記錄啊民轴。

  • + clearTextInputContextIdentifier:
+ (void)clearTextInputContextIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(7_0);

調(diào)用這個方法可以從程序的user default中移除與指定標(biāo)識相關(guān)的所有文本輸入模式。移除這些信息會讓響應(yīng)者重新使用默認(rèn)的文本輸入模式球订。

和上一個Identifier一樣沒搞懂后裸、期待有大神指正

  • inputAssistantItem
- (void)reloadInputViews NS_AVAILABLE_IOS(3_2);

iPad鍵盤上方的快捷方式欄。iPhone或iPod Touch上沒有快捷方式欄

包含用于管理文本的輸入建議和其他控件冒滩,例如剪切微驶,復(fù)制和粘貼命令


User Activities

從iOS 8起,蘋果為我們提供了一個非常棒的功能开睡,即Handoff因苹。使用這一功能,我們可以在一部iOS設(shè)備的某個應(yīng)用上開始做一件事篇恒,然后在另一臺iOS設(shè)備上繼續(xù)做這件事(比如在Mac上復(fù)制扶檐、在iPhone上粘貼)。Handoff的基本思想是用戶在一個應(yīng)用里所做的任何操作都可以看作是一個Activity胁艰,一個Activity可以和一個特定iCloud用戶的多臺設(shè)備關(guān)聯(lián)起來款筑。在編寫一個支持Handoff的應(yīng)用時,會有以下三個交互事件:

  1. 為將在另一臺設(shè)備上繼續(xù)做的事創(chuàng)建一個新的User Activity腾么;
  2. 當(dāng)需要時奈梳,用新的數(shù)據(jù)更新已有的User Activity
  3. 把一個User Activity傳遞到另一臺設(shè)備上解虱。

為了支持這些交互事件攘须,在iOS 8后,UIResponder類新增了幾個方法饭寺,我們在此不討論這幾個方法的實際使用阻课,想了解更多的話叫挟,可以參考iOS 8 Handoff 開發(fā)指南。我們在此只是簡單描述一下這幾個方法限煞。

  • userActivity
@property (nullable, nonatomic, strong) NSUserActivity *userActivity NS_AVAILABLE_IOS(8_0);

由UIKit管理的User Activities

會在適當(dāng)?shù)臅r間自動保存抹恳。一般情況下,我們可以重寫UIResponder類的updateUserActivityState:方法來延遲添加表示User Activity的狀態(tài)數(shù)據(jù)署驻。當(dāng)我們不再需要一個User Activity時奋献,我們可以設(shè)置userActivity屬性為nil。任何由UIKit管理的NSUserActivity對象旺上,如果它沒有相關(guān)的響應(yīng)者瓶蚂,則會自動失效。

  • - updateUserActivityState:
- (void)updateUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0);

更新用戶的活動宣吱。

你也可以理解成發(fā)送窃这、這個方法會由系統(tǒng)自動調(diào)用。
你可以向這個avtivity添加數(shù)據(jù)然后調(diào)用super進(jìn)行發(fā)送征候。

  • - restoreUserActivityState:
- (void)restoreUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0);

恢復(fù)用戶的操作

你可以在這里獲取到userActivity用于恢復(fù)用戶操作杭攻。
這個方法需要被子類重寫、并且由AppDelegate調(diào)用

- (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。就是類似在主屏幕搜索微博內(nèi)容的功能跑揉。
或許你可以參考一下《iOS Search API - NSUserActivity》


UITouch

//觸摸事件在屏幕上有一個周期
typedef NS_ENUM(NSInteger, UITouchPhase) {
    UITouchPhaseBegan,           //開始觸摸  
    UITouchPhaseMoved,           //移動    
    UITouchPhaseStationary,      //停留
    UITouchPhaseEnded,            //觸摸結(jié)束
    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

//觸摸產(chǎn)生或變化的時間戳 只讀
@property(nonatomic,readonly) NSTimeInterval      timestamp;
//觸摸周期內(nèi)的各個狀態(tài)
@property(nonatomic,readonly) UITouchPhase        phase;
//短時間內(nèi)點擊的次數(shù) 只讀
@property(nonatomic,readonly) NSUInteger          tapCount;   

//獲取手指與屏幕的接觸半徑 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的坐標(biāo)系的(以view的左上角為原點(0,0))
// 調(diào)用時傳入的view參數(shù)為nil的話锅睛,返回的是觸摸點在UIWindow的位置
- (CGPoint)locationInView:(nullable UIView *)view;
//該方法記錄了前一個觸摸點的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;

//獲取觸摸壓力值,一般的壓力感應(yīng)值為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{
    // 想讓控件隨著手指移動而移動,監(jiān)聽手指移動
    // 獲取UITouch對象
    UITouch *touch = [touches anyObject];
    // 獲取當(dāng)前點的位置
    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:會把之前形變給清空,重新開始設(shè)置形變參數(shù)
    // make:相對于最原始的位置形變
    // CGAffineTransform t:相對這個t的形變的基礎(chǔ)上再去形變
    // 如果相對哪個形變再次形變,就傳入它的形變
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
    
}

UIEvent

//事件類型
typedef NS_ENUM(NSInteger, UIEventType) {
    UIEventTypeTouches,//觸摸事件(通過觸摸现拒、手勢進(jìn)行觸發(fā),例如手指點擊、縮放)
    UIEventTypeMotion,//運(yùn)動事件,通過加速器進(jìn)行觸發(fā)(例如手機(jī)晃動)
    UIEventTypeRemoteControl,//遠(yuǎn)程控制事件通過其他遠(yuǎn)程設(shè)備觸發(fā)(例如耳機(jī)控制按鈕)
};

 // 觸摸事件的類型
typedef NS_ENUM(NSInteger, UIEventSubtype) {
    
    UIEventSubtypeNone                              = 0,
    //搖晃 
    UIEventSubtypeMotionShake                       = 1,
   //播放
    UIEventSubtypeRemoteControlPlay                 = 100,
   //暫停
    UIEventSubtypeRemoteControlPause                = 101,
    //停止
    UIEventSubtypeRemoteControlStop                 = 102,
    //播放和暫停切換 
    UIEventSubtypeRemoteControlTogglePlayPause      = 103,
    //下一首
    UIEventSubtypeRemoteControlNextTrack            = 104,
    //上一首
    UIEventSubtypeRemoteControlPreviousTrack        = 105,
     //開始后退 
    UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
    //結(jié)束后退 
    UIEventSubtypeRemoteControlEndSeekingBackward   = 107,
    //開始快進(jìn) 
    UIEventSubtypeRemoteControlBeginSeekingForward  = 108,
    //結(jié)束快進(jìn)
    UIEventSubtypeRemoteControlEndSeekingForward    = 109,
};


NS_CLASS_AVAILABLE_IOS(2_0) @interface UIEvent : NSObject
//事件類型
@property(nonatomic,readonly) UIEventType     type NS_AVAILABLE_IOS(3_0);
// 觸摸事件的類型
@property(nonatomic,readonly) UIEventSubtype  subtype NS_AVAILABLE_IOS(3_0);

//事件的時間戳
@property(nonatomic,readonly) NSTimeInterval  timestamp;

//所有的觸摸 
- (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 數(shù)組中望侈,你可以用 coalescedTouchesForTouch(_:) 方法來訪問
- (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0);
//輔助UITouch的觸摸具练,預(yù)測發(fā)生了一系列主要的觸摸事件。這些預(yù)測可能不完全匹配的觸摸的真正的行為甜无,因為它的移動扛点,所以他們應(yīng)該被解釋為一個估計。
- (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0);

@end


Touch Event岂丘、UIControl陵究、UIGestureRecognizer三兄弟的恩怨情仇

《iOS基礎(chǔ)補(bǔ)完計劃--透過堆棧看事件響應(yīng)機(jī)制》


最后

本文主要是自己的學(xué)習(xí)與總結(jié)奥帘。如果文內(nèi)存在紕漏铜邮、萬望留言斧正。如果愿意補(bǔ)充以及不吝賜教小弟會更加感激。


參考資料

官方文檔 - UIResponder
解析iOS開發(fā)中的FirstResponder第一響應(yīng)對象
UIKit: UIResponder
iOS事件傳遞
iOS觸摸事件詳解
你真的了解UIEvent松蒜、UITouch嗎扔茅?
iOS開發(fā)中的NSUndoManager的undo/redo功能(一顆后悔藥)
iOS8新特性擴(kuò)展(Extension)應(yīng)用之四——自定義鍵盤控件
深入講解iOS鍵盤三:自定義鍵盤的兩種方法
iOS開發(fā)inputView和inputAccessoryView
談UITextView秸苗、UITextField的InPutView和AccessoryInputView的便利
iOS自定義鍵盤切換效果
textInputMode
iOS Search API - NSUserActivity
iOS點擊事件和手勢沖突
史上最詳細(xì)的iOS之事件的傳遞和響應(yīng)機(jī)制-原理篇
iOS觸摸事件全家桶

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末召娜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惊楼,更是在濱河造成了極大的恐慌玖瘸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件檀咙,死亡現(xiàn)場離奇詭異雅倒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弧可,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蔑匣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棕诵,你說我怎么就攤上這事殖演。” “怎么了年鸳?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丸相。 經(jīng)常有香客問我搔确,道長,這世上最難降的妖魔是什么灭忠? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任膳算,我火速辦了婚禮,結(jié)果婚禮上弛作,老公的妹妹穿的比我還像新娘涕蜂。我一直安慰自己,他們只是感情好映琳,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布机隙。 她就那樣靜靜地躺著,像睡著了一般萨西。 火紅的嫁衣襯著肌膚如雪有鹿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天谎脯,我揣著相機(jī)與錄音葱跋,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛娱俺,可吹牛的內(nèi)容都是我干的稍味。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼荠卷,長吁一口氣:“原來是場噩夢啊……” “哼模庐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起僵朗,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赖欣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后验庙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顶吮,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年粪薛,在試婚紗的時候發(fā)現(xiàn)自己被綠了悴了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡违寿,死狀恐怖湃交,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情藤巢,我是刑警寧澤搞莺,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站掂咒,受9級特大地震影響才沧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绍刮,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一温圆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧孩革,春花似錦岁歉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饱搏,卻和暖如春帆啃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窍帝。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工努潘, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卫袒。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓有序,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蜻拨。 傳聞我的和親對象是個殘疾皇子压怠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345