`
836811384
  • 浏览: 570069 次
文章分类
社区版块
存档分类
最新评论

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)


代码解读

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

  1. @interfaceELTableViewController:UIViewController
  2. <
  3. UITableViewDelegate,
  4. UITableViewDataSource,
  5. EGORefreshTableHeaderDelegate,
  6. LoadMoreTableFooterDelegate,
  7. IconDownloaderDelegate
  8. >

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

  1. //blocksforUITableViewdelegate
  2. typedefUITableViewCell*(^cellForRowAtIndexPathDelegate)(UITableView*,NSIndexPath*);
  3. typedefCGFloat(^heightForRowAtIndexPathDelegate)(UITableView*,NSIndexPath*);
  4. typedefvoid(^didSelectRowAtIndexPathDelegate)(UITableView*,NSIndexPath*);
  5. //blocksforrefreshandloadmore
  6. typedefvoid(^refreshDataSourceFunc)(void);
  7. typedefvoid(^loadMoreDataSourceFunc)(void);
  8. typedefvoid(^refreshDataSourceCompleted)(void);
  9. typedefvoid(^loadMoreDataSourceCompleted)(void);
  10. //usetoloadimage(async)
  11. typedefvoid(^loadImagesForVisiableRowsFunc)(void);
  12. typedefvoid(^appImageDownloadCompleted)(NSIndexPath*);


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

  1. //propertyforblocks
  2. @property(nonatomic,copy)cellForRowAtIndexPathDelegatecellForRowAtIndexPathDelegate;
  3. @property(nonatomic,copy)heightForRowAtIndexPathDelegateheightForRowAtIndexPathDelegate;
  4. @property(nonatomic,copy)didSelectRowAtIndexPathDelegatedidSelectRowAtIndexPathDelegate;
  5. @property(nonatomic,copy)loadMoreDataSourceFuncloadMoreDataSourceFunc;
  6. @property(nonatomic,copy)refreshDataSourceFuncrefreshDataSourceFunc;
  7. @property(nonatomic,copy)refreshDataSourceCompletedrefreshDataSourceCompleted;
  8. @property(nonatomic,copy)loadMoreDataSourceCompletedloadMoreDataSourceCompleted;
  9. @property(nonatomic,copy)loadImagesForVisiableRowsFuncloadImagesForVisiableRowsFunc;
  10. @property(nonatomic,copy)appImageDownloadCompletedappImageDownloadCompleted;

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

  1. -(id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView
  2. andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView;
  3. -(id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView
  4. andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView
  5. andTableViewFrame:(CGRect)frame;

  1. #pragmamark-UITableViewDelegate-
  2. -(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{
  3. if(nil==self.dataSource){
  4. return0;
  5. }
  6. return[self.dataSourcecount];
  7. }
  8. -(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{
  9. if(!self.cellForRowAtIndexPathDelegate){
  10. @throw[NSExceptionexceptionWithName:@"FrameworkError"
  11. reason:@"MustbesettingcellForRowAtIndexPathBlockforUITableView"userInfo:nil];
  12. }
  13. returnself.cellForRowAtIndexPathDelegate(tableView,indexPath);
  14. }
  15. -(CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath{
  16. if(!self.heightForRowAtIndexPathDelegate){
  17. @throw[NSExceptionexceptionWithName:@"FrameworkError"
  18. reason:@"MustbesettingheightForRowAtIndexPathDelegateforUITableView"userInfo:nil];
  19. }
  20. returnself.heightForRowAtIndexPathDelegate(tableView,indexPath);
  21. }
  22. -(void)tableView:(UITableView*)tableViewdidSelectRowAtIndexPath:(NSIndexPath*)indexPath{
  23. if(self.didSelectRowAtIndexPathDelegate){
  24. self.didSelectRowAtIndexPathDelegate(tableView,indexPath);
  25. }
  26. }
  27. #pragmamark-LoadMoreTableFooterDelegateMethods-
  28. -(void)loadMoreTableFooterDidTriggerRefresh:(LoadMoreTableFooterView*)view{
  29. if(self.loadMoreDataSourceFunc&&self.loadMoreDataSourceCompleted){
  30. self.loadMoreDataSourceFunc();
  31. doubledelayInSeconds=3.0;
  32. dispatch_time_tpopTime=dispatch_time(DISPATCH_TIME_NOW,delayInSeconds*NSEC_PER_SEC);
  33. dispatch_after(popTime,dispatch_get_main_queue(),
  34. self.loadMoreDataSourceCompleted);
  35. }
  36. }
  37. -(BOOL)loadMoreTableFooterDataSourceIsLoading:(LoadMoreTableFooterView*)view{
  38. returnself.isLoadingMore;
  39. }
  40. #pragmamark-EGORefreshTableHeaderDelegateMethods-
  41. -(void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView*)view{
  42. if(self.refreshDataSourceFunc&&self.refreshDataSourceCompleted){
  43. self.refreshDataSourceFunc();
  44. doubledelayInSeconds=3.0;
  45. dispatch_time_tpopTime=dispatch_time(DISPATCH_TIME_NOW,delayInSeconds*NSEC_PER_SEC);
  46. dispatch_after(popTime,dispatch_get_main_queue(),
  47. self.refreshDataSourceCompleted);
  48. }
  49. }
  50. -(BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView*)view{
  51. returnself.isRefreshing;
  52. }
  53. -(NSDate*)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView*)view{
  54. return[NSDatedate];
  55. }
  56. #pragmamark-UIScrollViewDelegateMethods-
  57. -(void)scrollViewWillBeginDecelerating:(UIScrollView*)scrollView{
  58. self.currentOffsetPoint=scrollView.contentOffset;
  59. }
  60. -(void)scrollViewDidScroll:(UIScrollView*)scrollView{
  61. CGPointpt=scrollView.contentOffset;
  62. if(self.currentOffsetPoint.y<pt.y){
  63. [self.loadMoreFooterViewloadMoreScrollViewDidScroll:scrollView];
  64. }else{
  65. [self.refreshHeaderViewegoRefreshScrollViewDidScroll:scrollView];
  66. }
  67. }
  68. -(void)scrollViewDidEndDragging:(UIScrollView*)scrollViewwillDecelerate:(BOOL)decelerate{
  69. CGPointpt=scrollView.contentOffset;
  70. if(self.currentOffsetPoint.y<pt.y){
  71. [self.loadMoreFooterViewloadMoreScrollViewDidEndDragging:scrollView];
  72. }else{
  73. [self.refreshHeaderViewegoRefreshScrollViewDidEndDragging:scrollView];
  74. }
  75. if(!decelerate&&self.loadImagesForVisiableRowsFunc){
  76. self.loadImagesForVisiableRowsFunc();
  77. }
  78. }
  79. -(void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView{
  80. if(self.loadImagesForVisiableRowsFunc){
  81. self.loadImagesForVisiableRowsFunc();
  82. }
  83. }
  84. #pragmamark-downloadimageasync-
  85. -(void)appImageDidLoad:(NSIndexPath*)indexPath{
  86. if(self.appImageDownloadCompleted){
  87. self.appImageDownloadCompleted(indexPath);
  88. }
  89. }

ELTableViewController 的使用

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

override父类的initBlocks方法:

  1. #pragmamark-privatemethods-
  2. -(void)loadDataSource{
  3. self.dataSource=[NSMutableArrayarray];
  4. [self.dataSourceaddObject:@"dataSource_1"];
  5. [self.dataSourceaddObject:@"dataSource_2"];
  6. [self.dataSourceaddObject:@"dataSource_3"];
  7. [self.dataSourceaddObject:@"dataSource_4"];
  8. [self.dataSourceaddObject:@"dataSource_5"];
  9. [self.dataSourceaddObject:@"dataSource_6"];
  10. [self.dataSourceaddObject:@"dataSource_7"];
  11. [self.dataSourceaddObject:@"dataSource_8"];
  12. [self.dataSourceaddObject:@"dataSource_9"];
  13. [self.dataSourceaddObject:@"dataSource_10"];
  14. }
  15. -(void)initBlocks{
  16. __blockTestViewController*blockedSelf=self;
  17. //loadmore
  18. self.loadMoreDataSourceFunc=^{
  19. [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_1"];
  20. [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_2"];
  21. [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_3"];
  22. [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_4"];
  23. [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_5"];
  24. blockedSelf.isLoadingMore=YES;
  25. [self.tableViewreloadData];
  26. NSLog(@"loadMoreDataSourceBlockwasinvoked");
  27. };
  28. //loadmorecompleted
  29. self.loadMoreDataSourceCompleted=^{
  30. blockedSelf.isLoadingMore=NO;
  31. [blockedSelf.loadMoreFooterViewloadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];
  32. NSLog(@"afterloadMorecompleted");
  33. };
  34. //refresh
  35. self.refreshDataSourceFunc=^{
  36. blockedSelf.dataSource=[NSMutableArrayarray];
  37. [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_1"];
  38. [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_2"];
  39. [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_3"];
  40. [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_4"];
  41. [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_5"];
  42. blockedSelf.isRefreshing=YES;
  43. [self.tableViewreloadData];
  44. NSLog(@"refreshDataSourceBlockwasinvoked");
  45. };
  46. //refreshcompleted
  47. self.refreshDataSourceCompleted=^{
  48. blockedSelf.isRefreshing=NO;
  49. [blockedSelf.loadMoreFooterViewloadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];
  50. NSLog(@"afterrefreshcompleted");
  51. };
  52. self.cellForRowAtIndexPathDelegate=^(UITableView*tableView,NSIndexPath*indexPath){
  53. staticNSString*cellIdentifier=@"cellIdentifier";
  54. UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:cellIdentifier];
  55. if(!cell){
  56. cell=[[[UITableViewCellalloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:cellIdentifier]autorelease];
  57. }
  58. cell.textLabel.text=[blockedSelf.dataSourceobjectAtIndex:indexPath.row];
  59. NSLog(@"block:cellForRowAtIndexPathBlockhasbeeninvoked.");
  60. returncell;
  61. };
  62. self.heightForRowAtIndexPathDelegate=^(UITableView*tableView,NSIndexPath*indexPath){
  63. NSLog(@"block:heightForRowAtIndexPathBlockhasbeeninvoked.");
  64. return60.0f;
  65. };
  66. self.didSelectRowAtIndexPathDelegate=^(UITableView*tableView,NSIndexPath*indexPath){
  67. NSLog(@"block:didSelectRowAtIndexPathDelegatehasbeeninvoked.");
  68. };
  69. }

然后在ViewDidLoad中调用:

  1. [selfinitBlocks];
  2. [selfloadDataSource];
  3. [self.tableViewreloadData];

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

  1. self.viewController=[[[TestViewControlleralloc]initWithRefreshHeaderViewEnabled:YESandLoadMoreFooterViewEnabled: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

分享到:
评论

相关推荐

    iphone UITableView异步加载图片

    在iOS开发中,UITableView是一种常见的UI组件,用于展示大量数据列表。然而,当这个列表包含大量图片时,如果采用同步加载的方式...因此,理解并掌握iPhone UITableView异步加载图片的技术对于iOS开发者来说至关重要。

    UITableView的封装

    在实际项目中,可能还需要处理一些特殊需求,例如下拉刷新、上拉加载更多。这时,我们可以集成第三方库,如SDRefreshControl,或者自定义手势来实现这些功能。 最后,别忘了写好注释和文档,清晰地解释每个方法和...

    UITableView的带有图片

    可以使用`imageView.sd_setImage(with: placeholder:)`方法加载图片。 - Kingfisher:同样提供了类似的功能,支持预加载、占位图等特性,使用`imageView.kf.setImage(with:)`进行图片加载。 4. **图片尺寸和性能...

    iOS-自定义下拉刷新上拉加载(可根据自己的需求改)

    在iOS开发中,用户界面的交互性和动态性是提升用户体验的关键因素之一,而下拉刷新和上拉加载功能就是这种体验的重要组成部分。本教程将详细介绍如何在iOS项目中实现自定义的下拉刷新和上拉加载,使得开发者可以根据...

    下拉刷新上拉加载的TableView封装

    在iOS开发中,"下拉刷新上拉加载"是一种常见的用户交互模式,广泛应用于列表视图(UITableView)中,以提供更好的数据加载体验。这种功能允许用户在滚动到列表顶部时触发新数据的加载(下拉刷新),而在接近列表底部...

    IOS自定义UITableView框架(社区风格)

    8. **性能优化**:使用异步加载图片库(如SDWebImage)来避免阻塞主线程,同时通过复用Cell减少内存消耗。还可以考虑使用CollectionView而非TableView,当内容更复杂且需要水平滑动时。 9. **扩展性**:设计框架时...

    ios-tableView的简单封装.zip

    这个名为“ios-tableView的简单封装.zip”的压缩包,显然提供了一个轻量级的UITableView封装库,名为QQtableView,作者通过GitHub分享了他的代码。从描述来看,该库主要实现了三大功能:下拉刷新、空页面处理以及...

    iOS开发 - 第04篇 - 网络 - 01 - NSOperation & 网络基础

    为了提高用户体验,我们通常会在Cell中使用异步加载图片或其他资源,避免阻塞主线程。这里,我们可以结合NSOperation和URLSession,创建一个自定义的下载操作,将每个下载任务包装成一个NSOperation实例,然后将其...

    ios-TableView delegate dataSource封装.zip

    这个“ios-TableView delegate dataSource封装.zip”文件显然提供了一个关于如何封装这两个协议的方法,以便在多个UITableView实例中重用代码,避免了每次创建新的表格视图时都需要手动复制和粘贴相同的数据源和代理...

    ios应用源码之从下往下拉进行列表内容动态加载 2018127

    在iOS应用开发中,从下往上拉进行列表内容动态加载是一种常见的用户体验设计,通常被称为“无限滚动”或“上拉加载更多”。这个功能允许用户在滚动到底部时自动加载更多的数据,无需离开当前页面,提高了浏览效率。...

    ios-自定义上拉加载和下拉刷新.zip

    在iOS开发中,用户界面的交互体验至关重要,其中上拉加载和下拉刷新功能是现代应用常见的设计元素,能够提供流畅的数据更新体验。本项目"ios-自定义上拉加载和下拉刷新.zip"旨在教你如何利用自己的基类实现一个...

    ios-图片轮播器(无限滚动).zip

    4. **支持本地图片和网络图片**:这意味着轮播器不仅能加载本地存储的图片,还能从网络上动态获取。对于网络图片,可能使用了URL加载系统,如NSURLSession或第三方库如AFNetworking,来异步下载图片并在下载完成时...

    EGO上拉下拉封装

    在iOS开发中,EGO(Ego Refreshing)框架是一个常用的第三方库,专门用于实现上拉刷新和下拉加载的功能。这种技术可以让用户在浏览列表时轻松获取更多数据,提升用户体验。本文将深入探讨EGO上拉下拉封装的相关知识...

    swift-SwiftiOS:一个简陋的TableView封装

    8. **性能优化**:为了提升用户体验,项目可能采用了异步加载数据、懒加载图片等优化策略。 9. **协议与委托设计模式**:封装库可能采用了协议和委托设计模式,使得业务逻辑和视图层解耦,提高了代码的可测试性和...

    swift-基于iOS8.0以上封装Photos简易使用Demo上手快简单通俗易懂

    本教程将带你了解如何基于iOS 8.0及以上版本,使用Swift来封装Photos框架,实现相册选择和图片处理的功能。这个"swift-基于iOS8.0以上封装Photos简易使用Demo上手快简单通俗易懂"的项目,旨在帮助开发者快速掌握这一...

    ios-跑马灯轮子封装思路.zip

    在iOS开发中,"跑马灯"通常指的是一个无限循环滚动的效果,常见于广告轮播、新闻...总的来说,这个"ios-跑马灯轮子封装思路.zip"文件可能提供了一个基础的实现框架,开发者可以在此基础上根据具体需求进行完善和优化。

    iOS企业培训视频Object-C基础等教程 就业班全套课程 从基础到就业不是梦

    ### iOS企业培训视频Object-C基础等教程就业班全套课程知识点概览 #### 一、基础知识篇 ##### 1.1 iOS开发概述 - **iOS系统介绍**:了解iOS操作系统的特性和版本发展历史。 - **iOS开发环境搭建**:安装Xcode集成...

    ios-任务管理器.zip

    - 为了提供流畅的用户体验,需要考虑内存管理和异步加载策略,避免阻塞主线程。 - 使用 Instruments 工具进行性能分析,找出并解决内存泄漏和性能瓶颈。 以上是对“ios-任务管理器”的核心知识点的解析,实际项目...

    ios-搜索界面的封装.zip

    6. **性能优化**:为了提高用户体验,可以考虑使用异步加载或分页加载技术,尤其是当数据源很大的时候。这样,用户在输入时不会感到卡顿,同时也可以节省内存。 7. **用户反馈和提示**:在搜索界面,可以添加即时...

    iOS面试题-千峰教育欧阳大神整理

    在iOS面试过程中,掌握核心知识点和技术是至关重要的。欧阳坚,作为千峰教育的大神,整理了一份全面的面试题集,涵盖了C/C++/Objective-C/iOS UI等多个方面。这份资料旨在帮助求职者深入理解iOS开发的核心概念,提高...

Global site tag (gtag.js) - Google Analytics