写在前面
做过移动端开发的人都知道,列表控件是最常用的控件之一。iOS里的列表控件是UITableView,其实Apple的开发人员对于UITableView的设计已经够好的了(简单易用,扩展性非常强等等)。
但对于展示逻辑单一的移动端系统软件,你还是能感觉到有些繁琐(或许是程序员天生就有些懒惰的毛病吧)。
冲着这些,今天就把UITableView常见的使用模式封装了一下。具体做了以下几件事:
1、 内嵌了下拉刷新(EGORefreshTableHeaderView)、上提加载更多(LoadMoreTableFooterView)
2、 内置实现了UITableViewDataSource、UITableViewDelegate这两个通常必须实现的协议,对于自实现的逻辑以Block的形式对客户代码开放
3、 内置实现了1中提到的两个组件的回调协议,同上,自实现的逻辑以Block的形式对外开放
4、 内置实现了EGORefreshTableHeaderView、LoadMoreTableFooterView与UITableView交互必须实现的UIScrollViewDelegate协议
2、 LoadMoreTableFooterView(修改版,原版不能适应任何尺寸的高度)
代码解读
它已经内置实现了这些协议,所以在你使用它的时候,无需设置和实现。
@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 *);
//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;
//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;
对于上提加载更多、下拉刷新、图片异步加载这几个功能都是可选的,它们以组件的形式存在。比如,在实例化该controller的时候你就可以设置上提和下拉是否可用。而对于图片下载,你只要不实现其相应得block,它也不会对你造成额外的负担。
- (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);
- }
- }
#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);
}
}
ELTableViewController 的使用
创建一个新的controller继承自:ELTableViewController;
- #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.");
- };
- }
#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.");
};
}
然后在ViewDidLoad中调用:
[self initBlocks];
[self loadDataSource];
[self.tableView reloadData];
最后,你在实例化该controller的时候,可以指定是否使用上提和下拉
self.viewController = [[[TestViewController alloc] initWithRefreshHeaderViewEnabled:YES andLoadMoreFooterViewEnabled:YES]autorelease];
写在最后
写完之后,我用它重构了一下快易博中,新浪微博的几个视图。也省掉了一些冗余代码,如果当初在开发的时候就使用它的话,感觉还是省了一些功夫的。
它其实也还是比较简单的封装,所以还不是很具有业务相关性,同时也可见它还有很多可继续增强的功能:
先写到这里吧。http://lldong.github.com/blog/2011/12/30/blocks/