ios UITableView封装之下拉-上提-图片异步加载

写在前面

做过移动端开发的人都知道,列表控件是最常用的控件之一。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)


代码解读

它已经内置实现了这些协议,所以在你使用它的时候,无需设置和实现。

[cpp]  view plain copy
  1. @interface ELTableViewController : UIViewController  
  2. <  
  3. UITableViewDelegate,  
  4. UITableViewDataSource,  
  5. EGORefreshTableHeaderDelegate,  
  6. LoadMoreTableFooterDelegate,  
  7. IconDownloaderDelegate  
  8. >  

对于不断变化的业务逻辑,这里提供了所有需要实现的block:

[cpp]  view plain copy
  1. //blocks for UITableView delegate  
  2. typedef UITableViewCell* (^cellForRowAtIndexPathDelegate) (UITableView *,NSIndexPath *);  
  3. typedef CGFloat (^heightForRowAtIndexPathDelegate) (UITableView *,NSIndexPath *);  
  4. typedef void (^didSelectRowAtIndexPathDelegate) (UITableView *,NSIndexPath *);  
  5.   
  6. //blocks for refresh and load more  
  7. typedef void (^refreshDataSourceFunc) (void);  
  8. typedef void (^loadMoreDataSourceFunc) (void);  
  9.   
  10. typedef void (^refreshDataSourceCompleted) (void);  
  11. typedef void (^loadMoreDataSourceCompleted) (void);  
  12. //use to load image (async)  
  13. typedef void (^loadImagesForVisiableRowsFunc) (void);  
  14. typedef void (^appImageDownloadCompleted) (NSIndexPath *);  


它们以属性的形式对外公开:

[cpp]  view plain copy
  1. //property for blocks  
  2. @property (nonatomic,copy) cellForRowAtIndexPathDelegate cellForRowAtIndexPathDelegate;  
  3. @property (nonatomic,copy) heightForRowAtIndexPathDelegate heightForRowAtIndexPathDelegate;  
  4. @property (nonatomic,copy) didSelectRowAtIndexPathDelegate didSelectRowAtIndexPathDelegate;  
  5.   
  6. @property (nonatomic,copy) loadMoreDataSourceFunc loadMoreDataSourceFunc;  
  7. @property (nonatomic,copy) refreshDataSourceFunc refreshDataSourceFunc;  
  8. @property (nonatomic,copy) refreshDataSourceCompleted refreshDataSourceCompleted;  
  9. @property (nonatomic,copy) loadMoreDataSourceCompleted loadMoreDataSourceCompleted;  
  10.   
  11. @property (nonatomic,copy) loadImagesForVisiableRowsFunc loadImagesForVisiableRowsFunc;  
  12. @property (nonatomic,copy) appImageDownloadCompleted appImageDownloadCompleted;  

对于上提加载更多、下拉刷新、图片异步加载这几个功能都是可选的,它们以组件的形式存在。比如,在实例化该controller的时候你就可以设置上提和下拉是否可用。而对于图片下载,你只要不实现其相应得block,它也不会对你造成额外的负担。

[cpp]  view plain copy
  1. - (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView   
  2. andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView;  
  3.   
  4. - (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView   
  5.           andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView   
  6.                      andTableViewFrame:(CGRect)frame;  

[cpp]  view plain copy
  1. #pragma mark - UITableView Delegate -  
  2. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{  
  3.     if (nil==self.dataSource) {  
  4.         return 0;  
  5.     }  
  6.       
  7.     return [self.dataSource count];  
  8. }  
  9.   
  10. - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{  
  11.     if (!self.cellForRowAtIndexPathDelegate) {  
  12.         @throw [NSException exceptionWithName:@"Framework Error"   
  13.                                        reason:@"Must be setting cellForRowAtIndexPathBlock for UITableView" userInfo:nil];  
  14.     }  
  15.     return self.cellForRowAtIndexPathDelegate(tableView,indexPath);  
  16. }  
  17.   
  18. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{  
  19.     if (!self.heightForRowAtIndexPathDelegate) {  
  20.         @throw [NSException exceptionWithName:@"Framework Error"   
  21.                                        reason:@"Must be setting heightForRowAtIndexPathDelegate for UITableView" userInfo:nil];  
  22.     }  
  23.     return self.heightForRowAtIndexPathDelegate(tableView,indexPath);  
  24. }  
  25.   
  26. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{  
  27.     if (self.didSelectRowAtIndexPathDelegate) {  
  28.         self.didSelectRowAtIndexPathDelegate(tableView,indexPath);  
  29.     }  
  30. }  
  31.   
  32. #pragma mark - LoadMoreTableFooterDelegate Methods -  
  33. - (void)loadMoreTableFooterDidTriggerRefresh:(LoadMoreTableFooterView *)view{  
  34.     if (self.loadMoreDataSourceFunc&&self.loadMoreDataSourceCompleted) {  
  35.         self.loadMoreDataSourceFunc();  
  36.           
  37.         double delayInSeconds = 3.0;  
  38.         dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);  
  39.         dispatch_after(popTime, dispatch_get_main_queue(),   
  40.                        self.loadMoreDataSourceCompleted);  
  41.     }  
  42. }  
  43.   
  44. - (BOOL)loadMoreTableFooterDataSourceIsLoading:(LoadMoreTableFooterView *)view{  
  45.     return self.isLoadingMore;  
  46. }  
  47.   
  48. #pragma mark - EGORefreshTableHeaderDelegate Methods -  
  49. -(void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView *)view{  
  50.     if (self.refreshDataSourceFunc&&self.refreshDataSourceCompleted){  
  51.         self.refreshDataSourceFunc();  
  52.           
  53.         double delayInSeconds = 3.0;  
  54.         dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);  
  55.         dispatch_after(popTime, dispatch_get_main_queue(),   
  56.                        self.refreshDataSourceCompleted);  
  57.     }  
  58. }  
  59.   
  60. -(BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView *)view{  
  61.     return self.isRefreshing;  
  62. }  
  63.   
  64. -(NSDate *)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView *)view{  
  65.     return [NSDate date];  
  66. }  
  67.   
  68. #pragma mark - UIScrollViewDelegate Methods -  
  69. -(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{  
  70.     self.currentOffsetPoint=scrollView.contentOffset;  
  71. }  
  72.   
  73. -(void)scrollViewDidScroll:(UIScrollView *)scrollView{  
  74.     CGPoint pt=scrollView.contentOffset;  
  75.     if (self.currentOffsetPoint.y<pt.y) {  
  76.         [self.loadMoreFooterView loadMoreScrollViewDidScroll:scrollView];  
  77.     }else {       
  78.         [self.refreshHeaderView egoRefreshScrollViewDidScroll:scrollView];  
  79.     }  
  80. }  
  81.   
  82. -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{  
  83.     CGPoint pt=scrollView.contentOffset;  
  84.     if (self.currentOffsetPoint.y<pt.y) {  
  85.         [self.loadMoreFooterView loadMoreScrollViewDidEndDragging:scrollView];  
  86.     }else {  
  87.         [self.refreshHeaderView egoRefreshScrollViewDidEndDragging:scrollView];  
  88.     }  
  89.       
  90.     if (!decelerate&&self.loadImagesForVisiableRowsFunc) {  
  91.         self.loadImagesForVisiableRowsFunc();  
  92.     }  
  93. }  
  94.   
  95. -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{  
  96.     if (self.loadImagesForVisiableRowsFunc) {  
  97.         self.loadImagesForVisiableRowsFunc();  
  98.     }  
  99. }  
  100.   
  101. #pragma mark - download image async -  
  102. -(void)appImageDidLoad:(NSIndexPath *)indexPath{  
  103.     if (self.appImageDownloadCompleted) {  
  104.         self.appImageDownloadCompleted(indexPath);  
  105.     }  
  106. }  

ELTableViewController 的使用

创建一个新的controller继承自:ELTableViewController;

override父类的initBlocks方法:

[cpp]  view plain copy
  1. #pragma mark - private methods -  
  2. - (void)loadDataSource{  
  3.     self.dataSource=[NSMutableArray array];  
  4.     [self.dataSource addObject:@"dataSource_1"];  
  5.     [self.dataSource addObject:@"dataSource_2"];  
  6.     [self.dataSource addObject:@"dataSource_3"];  
  7.     [self.dataSource addObject:@"dataSource_4"];  
  8.     [self.dataSource addObject:@"dataSource_5"];  
  9.     [self.dataSource addObject:@"dataSource_6"];  
  10.     [self.dataSource addObject:@"dataSource_7"];  
  11.     [self.dataSource addObject:@"dataSource_8"];  
  12.     [self.dataSource addObject:@"dataSource_9"];  
  13.     [self.dataSource addObject:@"dataSource_10"];  
  14. }  
  15.   
  16. - (void)initBlocks{  
  17.     __block TestViewController *blockedSelf=self;  
  18.       
  19.     //load more  
  20.     self.loadMoreDataSourceFunc=^{  
  21.         [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_1"];  
  22.         [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_2"];  
  23.         [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_3"];  
  24.         [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_4"];  
  25.         [blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_5"];  
  26.           
  27.         blockedSelf.isLoadingMore=YES;  
  28.         [self.tableView reloadData];  
  29.           
  30.         NSLog(@"loadMoreDataSourceBlock was invoked");  
  31.     };  
  32.       
  33.     //load more completed  
  34.     self.loadMoreDataSourceCompleted=^{  
  35.         blockedSelf.isLoadingMore=NO;  
  36.         [blockedSelf.loadMoreFooterView loadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];  
  37.           
  38.         NSLog(@"after loadMore completed");  
  39.     };  
  40.       
  41.     //refresh  
  42.     self.refreshDataSourceFunc=^{  
  43.         blockedSelf.dataSource=[NSMutableArray array];  
  44.         [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_1"];  
  45.         [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_2"];  
  46.         [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_3"];  
  47.         [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_4"];  
  48.         [blockedSelf.dataSource addObject:@"refreshDataSourceBlock_5"];  
  49.           
  50.         blockedSelf.isRefreshing=YES;  
  51.         [self.tableView reloadData];  
  52.           
  53.         NSLog(@"refreshDataSourceBlock was invoked");  
  54.     };  
  55.       
  56.     //refresh completed  
  57.     self.refreshDataSourceCompleted=^{  
  58.         blockedSelf.isRefreshing=NO;  
  59.         [blockedSelf.loadMoreFooterView loadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];  
  60.           
  61.         NSLog(@"after refresh completed");  
  62.     };  
  63.       
  64.     self.cellForRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){  
  65.         static NSString *cellIdentifier=@"cellIdentifier";  
  66.         UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];  
  67.         if (!cell) {  
  68.             cell=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]autorelease];  
  69.         }  
  70.           
  71.         cell.textLabel.text=[blockedSelf.dataSource objectAtIndex:indexPath.row];  
  72.           
  73.         NSLog(@"block:cellForRowAtIndexPathBlock has been invoked.");  
  74.           
  75.         return cell;  
  76.     };  
  77.       
  78.     self.heightForRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){  
  79.         NSLog(@"block:heightForRowAtIndexPathBlock has been invoked.");  
  80.         return 60.0f;  
  81.     };  
  82.       
  83.     self.didSelectRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){  
  84.         NSLog(@"block:didSelectRowAtIndexPathDelegate has been invoked.");  
  85.     };  
  86.       
  87. }  

然后在ViewDidLoad中调用:

[cpp]  view plain copy
  1. [self initBlocks];  
  2.     [self loadDataSource];  
  3.     [self.tableView reloadData];  

最后,你在实例化该controller的时候,可以指定是否使用上提和下拉

[cpp]  view plain copy
  1. 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

你可能感兴趣的:(ios UITableView封装之下拉-上提-图片异步加载)