做过移动端开发的人都知道,列表控件是最常用的控件之一。iOS里的列表控件是UITableView,其实Apple的开发人员对于UITableView的设计已经够好的了(简单易用,扩展性非常强等等)。
但对于展示逻辑单一的移动端系统软件,你还是能感觉到有些繁琐(或许是程序员天生就有些懒惰的毛病吧)。
来看看它到底繁琐在哪儿了。首先,它的使用频率太高了;第二,它通常不是只呈现一下数据就完事了,一般都会跟随下拉刷新、上提加载更多功能,当然通常还要跟网络下载数据、图片打交道;第三,MVC模式是ios开发的惯用模式,随之而来的是一大堆协议的实现(无论你是再写一次也好,拷贝也罢,反正做这些工作都让人觉得索然无味)。
冲着这些,今天就把UITableView常见的使用模式封装了一下。具体做了以下几件事:
1、 内嵌了下拉刷新(EGORefreshTableHeaderView)、上提加载更多(LoadMoreTableFooterView)
2、 内置实现了UITableViewDataSource、UITableViewDelegate这两个通常必须实现的协议,对于自实现的逻辑以Block的形式对客户代码开放
3、 内置实现了1中提到的两个组件的回调协议,同上,自实现的逻辑以Block的形式对外开放
4、 内置实现了EGORefreshTableHeaderView、LoadMoreTableFooterView与UITableView交互必须实现的UIScrollViewDelegate协议
5、 内置实现了异步图片下载(可选)
你可以到我的Github上,查看源码。称它为ELTableViewController是取了EGORefreshTableHeaderView以及LoadMoreTableFooterView的首字母。
这份代码中包含了一个示例程序以及三个必备组件:
1、 EGORefreshTableHeaderView
2、 LoadMoreTableFooterView(修改版,原版不能适应任何尺寸的高度)
3、 Apple官方提供的异步下载UITableView中的图片的示例组件(IconDownLoader),这个只适用于下载类似于社交网络中的用户头像,不建议使用它来下载那些大图片,因为它甚至都没有缓存(如果图片很大,推荐使用SDImage)
它已经内置实现了这些协议,所以在你使用它的时候,无需设置和实现。
@interface ELTableViewController : UIViewController < UITableViewDelegate, UITableViewDataSource, EGORefreshTableHeaderDelegate, LoadMoreTableFooterDelegate, IconDownloaderDelegate >
对于不断变化的业务逻辑,这里提供了所有需要实现的block:
//blocks for UITableView delegate typedef UITableViewCell* (^cellForRowAtIndexPathDelegate) (UITableView *,NSIndexPath *); typedef CGFloat (^heightForRowAtIndexPathDelegate) (UITableView *,NSIndexPath *); typedef void (^didSelectRowAtIndexPathDelegate) (UITableView *,NSIndexPath *); //blocks for refresh and load more typedef void (^refreshDataSourceFunc) (void); typedef void (^loadMoreDataSourceFunc) (void); typedef void (^refreshDataSourceCompleted) (void); typedef void (^loadMoreDataSourceCompleted) (void); //use to load image (async) typedef void (^loadImagesForVisiableRowsFunc) (void); typedef void (^appImageDownloadCompleted) (NSIndexPath *);
//property for blocks @property (nonatomic,copy) cellForRowAtIndexPathDelegate cellForRowAtIndexPathDelegate; @property (nonatomic,copy) heightForRowAtIndexPathDelegate heightForRowAtIndexPathDelegate; @property (nonatomic,copy) didSelectRowAtIndexPathDelegate didSelectRowAtIndexPathDelegate; @property (nonatomic,copy) loadMoreDataSourceFunc loadMoreDataSourceFunc; @property (nonatomic,copy) refreshDataSourceFunc refreshDataSourceFunc; @property (nonatomic,copy) refreshDataSourceCompleted refreshDataSourceCompleted; @property (nonatomic,copy) loadMoreDataSourceCompleted loadMoreDataSourceCompleted; @property (nonatomic,copy) loadImagesForVisiableRowsFunc loadImagesForVisiableRowsFunc; @property (nonatomic,copy) appImageDownloadCompleted appImageDownloadCompleted;
- (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView; - (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView andTableViewFrame:(CGRect)frame;
#pragma mark - UITableView Delegate - - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ if (nil==self.dataSource) { return 0; } return [self.dataSource count]; } - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ if (!self.cellForRowAtIndexPathDelegate) { @throw [NSException exceptionWithName:@"Framework Error" reason:@"Must be setting cellForRowAtIndexPathBlock for UITableView" userInfo:nil]; } return self.cellForRowAtIndexPathDelegate(tableView,indexPath); } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ if (!self.heightForRowAtIndexPathDelegate) { @throw [NSException exceptionWithName:@"Framework Error" reason:@"Must be setting heightForRowAtIndexPathDelegate for UITableView" userInfo:nil]; } return self.heightForRowAtIndexPathDelegate(tableView,indexPath); } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ if (self.didSelectRowAtIndexPathDelegate) { self.didSelectRowAtIndexPathDelegate(tableView,indexPath); } } #pragma mark - LoadMoreTableFooterDelegate Methods - - (void)loadMoreTableFooterDidTriggerRefresh:(LoadMoreTableFooterView *)view{ if (self.loadMoreDataSourceFunc&&self.loadMoreDataSourceCompleted) { self.loadMoreDataSourceFunc(); double delayInSeconds = 3.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), self.loadMoreDataSourceCompleted); } } - (BOOL)loadMoreTableFooterDataSourceIsLoading:(LoadMoreTableFooterView *)view{ return self.isLoadingMore; } #pragma mark - EGORefreshTableHeaderDelegate Methods - -(void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView *)view{ if (self.refreshDataSourceFunc&&self.refreshDataSourceCompleted){ self.refreshDataSourceFunc(); double delayInSeconds = 3.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), self.refreshDataSourceCompleted); } } -(BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView *)view{ return self.isRefreshing; } -(NSDate *)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView *)view{ return [NSDate date]; } #pragma mark - UIScrollViewDelegate Methods - -(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{ self.currentOffsetPoint=scrollView.contentOffset; } -(void)scrollViewDidScroll:(UIScrollView *)scrollView{ CGPoint pt=scrollView.contentOffset; if (self.currentOffsetPoint.y<pt.y) { [self.loadMoreFooterView loadMoreScrollViewDidScroll:scrollView]; }else { [self.refreshHeaderView egoRefreshScrollViewDidScroll:scrollView]; } } -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ CGPoint pt=scrollView.contentOffset; if (self.currentOffsetPoint.y<pt.y) { [self.loadMoreFooterView loadMoreScrollViewDidEndDragging:scrollView]; }else { [self.refreshHeaderView egoRefreshScrollViewDidEndDragging:scrollView]; } if (!decelerate&&self.loadImagesForVisiableRowsFunc) { self.loadImagesForVisiableRowsFunc(); } } -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ if (self.loadImagesForVisiableRowsFunc) { self.loadImagesForVisiableRowsFunc(); } } #pragma mark - download image async - -(void)appImageDidLoad:(NSIndexPath *)indexPath{ if (self.appImageDownloadCompleted) { self.appImageDownloadCompleted(indexPath); } }
创建一个新的controller继承自:ELTableViewController;
override父类的initBlocks方法:
#pragma mark - private methods - - (void)loadDataSource{ self.dataSource=[NSMutableArray array]; [self.dataSource addObject:@"dataSource_1"]; [self.dataSource addObject:@"dataSource_2"]; [self.dataSource addObject:@"dataSource_3"]; [self.dataSource addObject:@"dataSource_4"]; [self.dataSource addObject:@"dataSource_5"]; [self.dataSource addObject:@"dataSource_6"]; [self.dataSource addObject:@"dataSource_7"]; [self.dataSource addObject:@"dataSource_8"]; [self.dataSource addObject:@"dataSource_9"]; [self.dataSource addObject:@"dataSource_10"]; } - (void)initBlocks{ __block TestViewController *blockedSelf=self; //load more self.loadMoreDataSourceFunc=^{ [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_1"]; [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_2"]; [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_3"]; [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_4"]; [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_5"]; blockedSelf.isLoadingMore=YES; [self.tableView reloadData]; NSLog(@"loadMoreDataSourceBlock was invoked"); }; //load more completed self.loadMoreDataSourceCompleted=^{ blockedSelf.isLoadingMore=NO; [blockedSelf.loadMoreFooterView loadMoreScrollViewDataSourceDidFinishedLoading:self.tableView]; NSLog(@"after loadMore completed"); }; //refresh self.refreshDataSourceFunc=^{ blockedSelf.dataSource=[NSMutableArray array]; [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_1"]; [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_2"]; [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_3"]; [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_4"]; [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_5"]; blockedSelf.isRefreshing=YES; [self.tableView reloadData]; NSLog(@"refreshDataSourceBlock was invoked"); }; //refresh completed self.refreshDataSourceCompleted=^{ blockedSelf.isRefreshing=NO; [blockedSelf.loadMoreFooterView loadMoreScrollViewDataSourceDidFinishedLoading:self.tableView]; NSLog(@"after refresh completed"); }; self.cellForRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){ static NSString *cellIdentifier=@"cellIdentifier"; UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (!cell) { cell=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]autorelease]; } cell.textLabel.text=[blockedSelf.dataSource objectAtIndex:indexPath.row]; NSLog(@"block:cellForRowAtIndexPathBlock has been invoked."); return cell; }; self.heightForRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){ NSLog(@"block:heightForRowAtIndexPathBlock has been invoked."); return 60.0f; }; self.didSelectRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){ NSLog(@"block:didSelectRowAtIndexPathDelegate has been invoked."); }; }
[self initBlocks]; [self loadDataSource]; [self.tableView reloadData];
最后,你在实例化该controller的时候,可以指定是否使用上提和下拉
self.viewController = [[[TestViewController alloc] initWithRefreshHeaderViewEnabled:YES andLoadMoreFooterViewEnabled:YES]autorelease];
写完之后,我用它重构了一下快易博中,新浪微博的几个视图。也省掉了一些冗余代码,如果当初在开发的时候就使用它的话,感觉还是省了一些功夫的。
它其实也还是比较简单的封装,所以还不是很具有业务相关性,同时也可见它还有很多可继续增强的功能:
1、 封装增删改查功能
2、 封装加载、操作时动画
3、 封装网络加载的统一实现
……………….
先写到这里吧。
推荐两篇讲ios block非常不错的文章:
http://lldong.github.com/blog/2011/12/30/blocks/
http://yannickloriot.com/2011/11/working-with-blocks/
源码地址:
https://github.com/yanghua/ELTableViewController