您的位置:首页 > 产品设计 > UI/UE

iOS学习笔记13— UITableView封装之下拉-上提-图片异步加载

2013-02-28 16:43 495 查看
做过移动端开发的人都知道,列表控件是最常用的控件之一。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
plaincopy

@interface ELTableViewController : UIViewController

<

UITableViewDelegate,

UITableViewDataSource,

EGORefreshTableHeaderDelegate,

LoadMoreTableFooterDelegate,

IconDownloaderDelegate

>

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

[cpp] view
plaincopy

//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 *);

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

[cpp] view
plaincopy

//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,它也不会对你造成额外的负担。

[cpp] view
plaincopy

- (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView

andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView;

- (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView

andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView

andTableViewFrame:(CGRect)frame;

[cpp] view
plaincopy

#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方法:

[cpp] view
plaincopy

#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.");

};

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: