前言
在 iOS 中,對象間的交互模式大概有這幾種:直接 property 傳值、delegate、KVO、block、protocol、多態、Target-Action 等等,本文介紹的是一種基于 UIResponder 對象交互方式,簡而言之,就是 通過在 UIResponder上掛一個 category,使得事件和參數可以沿著 responder chain 逐步傳遞。對于那種 subviews 特別多,事件又需要層層傳遞的層級視圖特別好用,但是,缺點也很明顯,必須依賴于 UIResponder 對象。
在項目開發中相信很多朋友都遇到過多層級view,事件拋出至VC處理的問題。 一般的處理方法都是使用 代理、回調、屬性傳值,可是多層級的View會讓整個流程非常痛苦和難于維護。
多層級View的UI事件處理有較好的方案,比如采用ReactiveCocoa、使用通知等等。可是ReactiveCocoa 的學習成本比較高,通知的話注冊通知,發送通知也是比較麻煩。
場景
一個VC的View上放了很多的子視圖,(中間有很多層)我們點擊了最上面的一個Button,需要把Button的tag傳到 VC中
知識點( 此處不討論代理回調和通知。)
UIResponder類定義了一個對象接口用來響應和處理事件, 它是UIApplication, UIView以及UIView的子類(包括UIWindow)的父類, 這些類的實例對象被稱為響應對象或者響應者。
然后UIResponder對象有一個重要的屬性叫做nextResponder, 下一個響應者,可以保證找到當前view的事件的接收者
可以建立一個 UIResponder的類別,在類別中擴建一個方法,使所有的子類都可以調用.
在需要處理的地方重寫 UIResponder的類別中的這個方法即可使整個傳遞終結掉。大大優化了整個事件處理過程。
由上至下的事件傳遞實現方法
#import "UIResponder+Router.h"
@implementation UIResponder (Router)
- (void)routerWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
if (self.nextResponder) {
[[self nextResponder] routerWithEventName:eventName userInfo:userInfo];
}
}
@end
第一個參數是事件名稱, 第二個參數是需要傳遞的參數信息
看起來這樣一個方法會陷入死循環, 其實不然, 當self.nextResponder向上一直找到UIApplication都還不能響應事件的時候,
系統就會自動丟棄這個事件
而當我控制器中重寫這個方法的時候, 相當于重寫父類方法的時候,
那么系統就會走子類的方法, 那么參數就直接傳遞給控制器了
*控制器中重寫父類方法*
- (void)routerWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
if ([eventName isEqualToString:YFTransferNameEvent]) {
NSString * name = userInfo[YFUserName];
NSLog(@"用戶的姓名為:%@",name);
}
}
*cell中Button的點擊事件*
- (void)buttonClickAction:(UIButton *)sender
{
[sender routerWithEventName:YFTransferNameEvent
userInfo:@{ YFUserName:[self userName], }];
}
也就是說, button將事件處理傳遞給nextResponder, 也就是cell, cell沒有重寫父類方法, 繼續將事件傳遞給tableView, tableView也沒有重寫父類方法, 于是將事件處理傳遞給控制器的view,控制器的view也沒有重寫父類方法, 于是將事件處理傳遞給控制器, 控制器重寫了父類方法, 于是就走控制器重寫的方法, 進行事件處理, 事件就成功地從button傳到了控制器.
跨層處理事件后的回執
cell把事件傳遞給 VC后VC處理后怎么把結果返回給Cell使用呢,兩個方式:
在上述的方法中把需要接受結果的對象指針傳過去,比如cell上一個按鈕要設置背景圖片,VC取完圖片在方法中獲取到這個按鈕的指針,VC通過這個指針通過直接操作內存的方式設置這個按鈕即可。
在類別的方法中定義 Block回調函數,cell發送事件,VC處理完后,通過Block把處理結果發送給 cell,供cell使用,這樣是最簡單的。
值得注意的是,這樣的事件傳遞處理方法,最常見的Bug就是當前試圖初始化后確實存在,但是沒有加載到父視圖上,才導致的方法無法觸發。