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)


代码解读

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

@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;

对于上提加载更多、下拉刷新、图片异步加载这几个功能都是可选的,它们以组件的形式存在。比如,在实例化该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);
    }
}

ELTableViewController 的使用

创建一个新的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.");
    };
    
}

然后在ViewDidLoad中调用:

[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




原文发布时间为:2012-12-02


本文作者:vinoYang


本文来自云栖社区合作伙伴CSDN博客,了解相关信息可以关注CSDN博客。

目录
相关文章
|
2月前
|
JSON JavaScript 安全
iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据
iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据
24 1
|
8月前
|
iOS开发
iOS TextView插入表情或者图片后字体变大或变小
iOS TextView插入表情或者图片后字体变大或变小
67 1
|
13天前
|
存储 缓存 安全
基于iOS平台的高效图片缓存策略实现
【4月更文挑战第22天】 在移动应用开发中,图片资源的加载与缓存是影响用户体验的重要因素之一。尤其对于iOS平台,由于设备存储空间的限制以及用户对流畅性的高要求,设计一种合理的图片缓存策略显得尤为关键。本文将探讨在iOS环境下,如何通过使用先进的图片缓存技术,包括内存缓存、磁盘缓存以及网络请求的优化,来提高应用的性能和响应速度。我们将重点分析多级缓存机制的设计与实现,并对可能出现的问题及其解决方案进行讨论。
|
13天前
|
存储 缓存 算法
实现iOS平台的高效图片缓存策略
【4月更文挑战第22天】在移动应用开发中,图片资源的处理是影响用户体验的重要因素之一。特别是对于图像资源密集型的iOS应用,如何有效地缓存图片以减少内存占用和提升加载速度,是开发者们面临的关键挑战。本文将探讨一种针对iOS平台的图片缓存策略,该策略通过结合内存缓存与磁盘缓存的机制,并采用先进的图片解码和异步加载技术,旨在实现快速加载的同时,保持应用的内存效率。
|
26天前
|
存储 缓存 iOS开发
基于iOS的高效图片缓存策略实现
【4月更文挑战第9天】在移动应用开发中,图片资源的加载与缓存是影响用户体验的重要因素之一。特别是对于iOS平台,合理设计图片缓存策略不仅能够提升用户浏览图片时的流畅度,还能有效降低应用程序的内存压力。本文将介绍一种针对iOS环境优化的图片缓存技术,该技术通过多级缓存机制和内存管理策略,实现了图片快速加载与低内存消耗的目标。我们将从系统架构、关键技术细节以及性能评估等方面展开讨论,为开发者提供一套实用的图片缓存解决方案。
19 0
|
1月前
|
存储 缓存 iOS开发
实现iOS平台的高效图片缓存策略
【4月更文挑战第4天】在移动应用开发中,图片资源的加载与缓存是影响用户体验的关键因素之一。尤其对于iOS平台,由于设备存储和内存资源的限制,设计一个高效的图片缓存机制尤为重要。本文将深入探讨在iOS环境下,如何通过技术手段实现图片的高效加载与缓存,包括内存缓存、磁盘缓存以及网络层面的优化,旨在为用户提供流畅且稳定的图片浏览体验。
|
4月前
|
JSON JavaScript 安全
iOS 应用程序数据保护:如何保护 iOS 应用程序中的图片、资源和敏感数据
iOS 应用程序数据保护:如何保护 iOS 应用程序中的图片、资源和敏感数据
|
8月前
|
缓存 iOS开发
iOS LaunchScreen.storyboard 启动页设置图片不显示
iOS LaunchScreen.storyboard 启动页设置图片不显示
140 0
|
9月前
|
Shell iOS开发
iOS 逆向编程(九 - 2)将端口映射、USB连接手机封装成 .sh 脚本
iOS 逆向编程(九 - 2)将端口映射、USB连接手机封装成 .sh 脚本
92 0
|
9月前
|
前端开发 JavaScript 定位技术
iOS 逆向编程(十六)DZMCycript 脚本使用(封装了常用的快捷函数,后续会继续添加)
iOS 逆向编程(十六)DZMCycript 脚本使用(封装了常用的快捷函数,后续会继续添加)
102 0