開篇先說點廢話吧矮瘟,最近整個人都很煩躁,也許跟天氣有關(guān)吧塑娇,也很久沒有靜下來寫一些東西了澈侠,最近也一直忙著新項目,也很糾接新項目應(yīng)該采用什么樣的結(jié)構(gòu)去寫才好迭代埋酬、維護(hù)哨啃。最終按自己的一寫想法采用了Controller View ViewHander的模式(有點類似MVVM)烧栋,因為這個Demo按照這個想法來寫的所以這里簡單說下,就不過多的討論這個了棘催,回到主題上UIResponder來劲弦,沒有說之前我們先看一個圖我們開發(fā)中經(jīng)常遇到的:
很簡單就是在UITableViewCell 放了一個UIButton 那我們怎么樣接收這個Button的點擊事件? 你第一時間可能會想到Delegate,Block?的確它們都可以實現(xiàn)我們的需求醇坝,Delegate我們要多寫點代碼邑跪,Block 如果我們的事件邏輯復(fù)雜點就會再賦值時寫很多代碼,當(dāng)然你可以用一個簡單的Block把處理的業(yè)務(wù)代封裝成方法呼猪,再這個調(diào)用這方法画畅,也可以把代碼弄的簡潔點,最重要我一定要考慮循環(huán)引用的問題宋距。那我們能不能用UIResponder 傳遞這個事件呢轴踱,在我們想要的地方捕獲這個事件呢? 我們先來看看iOS 事件是怎么傳遞的我們看個圖:
如上圖,iOS中事件傳遞首先從App(UIApplication)開始谚赎,接著傳遞到Window(UIWindow)淫僻,在接著往下傳遞到View之前,Window會將事件交給GestureRecognizer壶唤,如果在此期間雳灵,GestureRecognizer識別了傳遞過來的事件,則該事件將不會繼續(xù)傳遞到View去闸盔,而是像我們之前說的那樣交給Target(ViewController)進(jìn)行處理悯辙。(注:詳細(xì)原理可以自己進(jìn)行搜索學(xué)習(xí))我們大致知道事件產(chǎn)生最先識別是的 AppDelegate,然后一層層往下找看事件發(fā)生那個view上迎吵,直到找個這個view,然后看個view 能不能響應(yīng)這個事件躲撰。那我們現(xiàn)在再說說響應(yīng)者鏈先看個張圖:
我知道了當(dāng)事觸摸事件發(fā)生,通過一層層找到的這個View ,找到這個View 后先判斷這個view能不能響應(yīng)這個事件击费,如果不能那就繼續(xù)找nextResponder我們看上面圖可以看出如果一個View有SuperView 那么這個View的nextResponder 就是他的SuperView拢蛋,如果沒有SuperView 那么它的nextResponder 就是他所在ViewController 然后就這樣一直找下去,直到找到或拋出異常蔫巩。
我們了解這機(jī)制后那我們怎么把這個UIButton Click 事件傳遞出來呢瓤狐,我們先來給UIResponder 添加一個我們自定義的事件,我就讓它傳遞我們這個事件出去批幌。
#import "UIResponder+Router.h"
@implementation UIResponder (Router)
// eventName 只是作個標(biāo)記础锐,當(dāng)我們需要在一個頁面?zhèn)鬟f個事件時我們可以進(jìn)區(qū)分,userInfo 為了省勁就沒有封裝荧缘,你可以針對性再封裝下
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
[[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}
@end
那我們怎么進(jìn)行傳遞呢皆警,那就是我們手動的去讓響應(yīng)者鏈傳遞這個事件
我們先看下工程的代碼文件:
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
// 這里為了省勁就沒有用單獨(dú)文件去寫,最好還是建兩個新文件去比較好
@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]];
}
//關(guān)鍵代碼就在這里, 我們在button click 事件中我再讓傳遞一個事件給響應(yīng)者鏈截粗,讓響應(yīng)者鏈傳出去
- (void)showNumberButtonClick:(id)sender {
// 我們在這個Click事件中去手動讓響應(yīng)者傳遞一個事件
[self.nextResponder routerEventWithName:@"showNumber" userInfo:@{@"object":self.textLabel.text}];
}
@end
主要的代碼差不多就是這些了信姓,至于他們的基類都是自己封裝好一部分鸵隧,還不怎么完善都是一些自己的想法。就不貼代碼稍后把這個Demo放出來意推。有興趣的可以下下來看看豆瘫,如果有我好的想法請聯(lián)系我:lsb332@163.com
我們先來看看在View 中捕獲下事件,在.m 文件我們導(dǎo)入UIResponder+Router.h頭文件 然后實現(xiàn)我們自定義的方法
#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 已經(jīng)貼出來過了這里就不貼了**
我們看下結(jié)果:
我們再來看看在UIViewController 捕獲菊值,代碼就不貼了看下結(jié)果好了:
總結(jié)
最重要的思想就是在響應(yīng)事件方法我們再主動的傳遞給響應(yīng)者鏈一個事件外驱,然后我們合適的地方去響應(yīng)這個事件
這個也是拋磚引玉的,自己理解的還很膚淺的腻窒,現(xiàn)在寫出來也算是自己學(xué)習(xí)的一個筆記吧昵宇,這個處理方法也是自己在集成環(huán)信中發(fā)現(xiàn)的,自己去摸索學(xué)習(xí)下儿子。
Demo地址 https://github.com/lsb332/UIResponderEX
在這里再說一下自己項目結(jié)構(gòu)瓦哎,為了減輕UIViewController 重量實行真正的MVC 把View分出來了,從而使ViewController 只負(fù)責(zé)view 的顯示 柔逼,稱除等蒋譬。因為我們項目經(jīng)常會用到TableView 為了不使View太重再次把這個分離去,使TableView的dataSource 在TableViewSource文件中去實現(xiàn)愉适,然后又給View 建了一個Handler 用來處理業(yè)務(wù)邏輯犯助,網(wǎng)絡(luò)請求等,然后又把handler 繼承一個網(wǎng)絡(luò)求的類儡毕,這樣可就可以處理的網(wǎng)絡(luò)的請求了也切,如果handler 處理完數(shù)據(jù)后可以通過Block 回調(diào)給View 或者直接把數(shù)據(jù)傳遞給TableViewSource 就可以直接刷新數(shù)據(jù)扑媚,不用再回調(diào)給View腰湾。這里只是簡單的說一下,有興趣的可以工程里看看疆股,還處在起步結(jié)段费坊,如果覺得成熟了再寫一篇文章說說吧。