最近也一直忙著新項目,也很糾接新項目應該采用什麼樣的結構去寫才好迭代、維護。最終按自己的一寫想法采用了Controller View ViewHander的模式(有點類似MVVM),因為這個Demo按照這個想法來寫的所以這裡簡單說下,就不過多的討論這個了,回到主題上UIResponder來,沒有說之前我們先看一個圖我們開發中經常遇到的:
很簡單就是在UITableViewCell 放了一個UIButton 那我們怎麼樣接收這個Button的點擊事件?你第一時間可能會想到Delegate,Block?
的確它們都可以實現我們的需求,Delegate我們要多寫點代碼,Block 如果我們的事件邏輯復雜點就會再賦值時寫很多代碼,當然你可以用一個簡單的Block把處理的業務代封裝成方法,再這個調用這方法,也可以把代碼弄的簡潔點,最重要我一定要考慮循環引用的問題。那我們能不能用UIResponder 傳遞這個事件呢,在我們想要的地方捕獲這個事件呢? 我們先來看看iOS 事件是怎麼傳遞的我們看個圖:
如上圖,iOS中事件傳遞首先從App(UIApplication)開始,接著傳遞到Window(UIWindow),在接著往下傳遞到View之前,Window會將事件交給GestureRecognizer,如果在此期間,GestureRecognizer識別了傳遞過來的事件,則該事件將不會繼續傳遞到View去,而是像我們之前說的那樣交給Target(ViewController)進行處理。(注:詳細原理可以自己進行搜索學習)我們大致知道事件產生最先識別是的 AppDelegate,然後一層層往下找看事件發生那個view上,直到找個這個view,然後看個view 能不能響應這個事件。那我們現在再說說響應者鏈先看個張圖:
我知道了當事觸摸事件發生,通過一層層找到的這個View ,找到這個View 後先判斷這個view能不能響應這個事件,如果不能那就繼續找nextResponder我們看上面圖可以看出如果一個View有SuperView 那麼這個View的nextResponder 就是他的SuperView,如果沒有SuperView 那麼它的nextResponder 就是他所在ViewController 然後就這樣一直找下去,直到找到或拋出異常。
我們了解這機制後那我們怎麼把這個UIButton Click 事件傳遞出來呢,我們先來給UIResponder 添加一個我們自定義的事件,我就讓它傳遞我們這個事件出去。
#import "UIResponder+Router.h"@implementation UIResponder (Router) // eventName 只是作個標記,當我們需要在一個頁面傳遞個事件時我們可以進區分,userInfo 為了省勁就沒有封裝,你可以針對性再封裝下- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo { [[self nextResponder] routerEventWithName:eventName userInfo:userInfo]; } @end
那我們怎麼進行傳遞呢,那就是我們手動的去讓響應者鏈傳遞這個事件
我們先看下工程的代碼文件:
View
#import "TestView.h"#import "TestViewTableDataSource.h"@implementation TestView { UITableView *_tableView; } - (instancetype)initWithController:(SBBaseViewController *)controller { self = [super initWithController:controller]; if (self) { [self setup]; } return self; } - (void)setup { _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _tableView.delegate = self; _tableView.rowHeight = 60; _tableView.translatesAutoresizingMaskIntoConstraints = NO; [_tableView registerClass:[TestViewTableCell class] forCellReuseIdentifier:@"cell"]; [self addSubview:_tableView]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[_tableView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_tableView)]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[_tableView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_tableView)]]; }
- (void)setHandler:(SBBaseHandler *)handler { [super setHandler:handler]; // 把tableViewDataSource 分離出去 TestViewTableDataSource *tableViewDataSoure = [[TestViewTableDataSource alloc] initWithTableView:_tableView]; _tableView.dataSource = tableViewDataSoure; self.handler.tableDataSource = tableViewDataSoure; } - (void)didLoad { [self.handler loadData]; }
Controller
#import "TestViewController.h"#import "TestView.h"#import "TestViewHandler.h"@interface TestViewController ()@end@implementation TestViewController- (void)loadView { [super loadView]; TestView *view = [[TestView alloc]initWithController:self]; TestViewHandler *handler = [[TestViewHandler alloc] init]; view.handler = handler; self.view = view; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.title = @"UIResponderEx"; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. }@end
Handler
#import "TestViewHandler.h"@implementation TestViewHandler- (void)loadData { NSMutableArray *datasource = [NSMutableArray arrayWithCapacity:10]; for (int i = 0; i< 10; ++i) { [datasource addObject:[NSString stringWithFormat:@"Row number is %d",i]]; } self.tableDataSource.dataSouce = [datasource copy]; }@end
TableDataSource
#import "SBBaseTableDataSource.h" @interface TestViewTableDataSource : SBBaseTableDataSource@end// 這裡為了省勁就沒有用單獨文件去寫,最好還是建兩個新文件去比較好 @interface TestViewTableCell : UITableViewCell @end
#import "TestViewTableDataSource.h"#import "UIResponder+Router.h"@implementation TestViewTableDataSource- (id)initWithTableView:(UITableView *)tableView { self = [super initWithTableView:tableView]; if (self) { } return self; } - (void)setDataSouce:(NSArray *)dataSouce { [super setDataSouce:dataSouce]; [self.tableView reloadData]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataSouce.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TestViewTableCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; cell.textLabel.text = self.dataSouce[indexPath.row]; return cell; }@end@implementation TestViewTableCell- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { [self setup]; } return self; } - (void)setup { UIButton *showNumberButton = [UIButton buttonWithType:UIButtonTypeCustom]; [showNumberButton setTitle:@"Show row number" forState:UIControlStateNormal]; showNumberButton.backgroundColor = [UIColor purpleColor]; showNumberButton.layer.cornerRadius = 4; showNumberButton.layer.masksToBounds = YES; showNumberButton.translatesAutoresizingMaskIntoConstraints = NO; [showNumberButton addTarget:self action:@selector(showNumberButtonClick:) forControlEvents:UIControlEventTouchUpInside]; [self.contentView addSubview:showNumberButton]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[showNumberButton(180)]-20-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(showNumberButton)]]; [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:showNumberButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]]; } //關鍵代碼就在這裡, 我們在button click 事件中我再讓傳遞一個事件給響應者鏈,讓響應者鏈傳出去- (void)showNumberButtonClick:(id)sender { // 我們在這個Click事件中去手動讓響應者傳遞一個事件 [self.nextResponder routerEventWithName:@"showNumber" userInfo:@{@"object":self.textLabel.text}]; }@end
主要的代碼差不多就是這些了,至於他們的基類都是自己封裝好一部分,還不怎麼完善都是一些自己的想法。就不貼代碼稍後把這個Demo放出來。有興趣的可以下下來看看,如果有我好的想法請聯系我:[email protected]。
我們先來看看在View 中捕獲下事件,在.m 文件我們導入UIResponder+Router.h頭文件 然後實現我們自定義的方法
#import "UIResponder+Router.h"#pragma UIResponder(Router)- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"View中捕獲" message:userInfo[@"object"] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil]; [alertView show]; }
手動傳遞事件的代碼TableDataSource 已經貼出來過了這裡就不貼了
我們看下結果:
我們再來看看在UIViewController 捕獲,代碼就不貼了看下結果好了:
總結
最重要的思想就是在響應事件方法我們再主動的傳遞給響應者鏈一個事件,然後我們合適的地方去響應這個事件。
這個也是拋磚引玉的,自己理解的還很膚淺的,現在寫出來也算是自己學習的一個筆記吧,這個處理方法也是自己在集成環信中發現的,自己去摸索學習下。
Demo地址:https://github.com/lsb332/UIResponderEX。
在這裡再說一下自己項目結構,為了減輕UIViewController 重量實行真正的MVC 把View分出來了,從而使ViewController 只負責view 的顯示 ,稱除等。因為我們項目經常會用到TableView 為了不使View太重再次把這個分離去,使TableView的dataSource 在TableViewSource文件中去實現,然後又給View 建了一個Handler 用來處理業務邏輯,網絡請求等,然後又把handler 繼承一個網絡求的類,這樣可就可以處理的網絡的請求了,如果handler 處理完數據後可以通過Block 回調給View 或者直接把數據傳遞給TableViewSource 就可以直接刷新數據,不用再回調給View。這裡只是簡單的說一下,有興趣的可以工程裡看看,還處在起步結段,如果覺得成熟了再寫一篇文章說說吧。