写在前面
做过移动端开发的人都知道,列表控件是最常用的控件之一。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)
代码解读
它已经内置实现了这些协议,所以在你使用它的时候,无需设置和实现。
- @interfaceELTableViewController:UIViewController
- <
- UITableViewDelegate,
- UITableViewDataSource,
- EGORefreshTableHeaderDelegate,
- LoadMoreTableFooterDelegate,
- IconDownloaderDelegate
- >
对于不断变化的业务逻辑,这里提供了所有需要实现的block:
- //blocksforUITableViewdelegate
- typedefUITableViewCell*(^cellForRowAtIndexPathDelegate)(UITableView*,NSIndexPath*);
- typedefCGFloat(^heightForRowAtIndexPathDelegate)(UITableView*,NSIndexPath*);
- typedefvoid(^didSelectRowAtIndexPathDelegate)(UITableView*,NSIndexPath*);
- //blocksforrefreshandloadmore
- typedefvoid(^refreshDataSourceFunc)(void);
- typedefvoid(^loadMoreDataSourceFunc)(void);
- typedefvoid(^refreshDataSourceCompleted)(void);
- typedefvoid(^loadMoreDataSourceCompleted)(void);
- //usetoloadimage(async)
- typedefvoid(^loadImagesForVisiableRowsFunc)(void);
- typedefvoid(^appImageDownloadCompleted)(NSIndexPath*);
- //propertyforblocks
- @property(nonatomic,copy)cellForRowAtIndexPathDelegatecellForRowAtIndexPathDelegate;
- @property(nonatomic,copy)heightForRowAtIndexPathDelegateheightForRowAtIndexPathDelegate;
- @property(nonatomic,copy)didSelectRowAtIndexPathDelegatedidSelectRowAtIndexPathDelegate;
- @property(nonatomic,copy)loadMoreDataSourceFuncloadMoreDataSourceFunc;
- @property(nonatomic,copy)refreshDataSourceFuncrefreshDataSourceFunc;
- @property(nonatomic,copy)refreshDataSourceCompletedrefreshDataSourceCompleted;
- @property(nonatomic,copy)loadMoreDataSourceCompletedloadMoreDataSourceCompleted;
- @property(nonatomic,copy)loadImagesForVisiableRowsFuncloadImagesForVisiableRowsFunc;
- @property(nonatomic,copy)appImageDownloadCompletedappImageDownloadCompleted;
对于上提加载更多、下拉刷新、图片异步加载这几个功能都是可选的,它们以组件的形式存在。比如,在实例化该controller的时候你就可以设置上提和下拉是否可用。而对于图片下载,你只要不实现其相应得block,它也不会对你造成额外的负担。
- -(id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView
- andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView;
- -(id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView
- andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView
- andTableViewFrame:(CGRect)frame;
- #pragmamark-UITableViewDelegate-
- -(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{
- if(nil==self.dataSource){
- return0;
- }
- return[self.dataSourcecount];
- }
- -(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{
- if(!self.cellForRowAtIndexPathDelegate){
- @throw[NSExceptionexceptionWithName:@"FrameworkError"
- reason:@"MustbesettingcellForRowAtIndexPathBlockforUITableView"userInfo:nil];
- }
- returnself.cellForRowAtIndexPathDelegate(tableView,indexPath);
- }
- -(CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath{
- if(!self.heightForRowAtIndexPathDelegate){
- @throw[NSExceptionexceptionWithName:@"FrameworkError"
- reason:@"MustbesettingheightForRowAtIndexPathDelegateforUITableView"userInfo:nil];
- }
- returnself.heightForRowAtIndexPathDelegate(tableView,indexPath);
- }
- -(void)tableView:(UITableView*)tableViewdidSelectRowAtIndexPath:(NSIndexPath*)indexPath{
- if(self.didSelectRowAtIndexPathDelegate){
- self.didSelectRowAtIndexPathDelegate(tableView,indexPath);
- }
- }
- #pragmamark-LoadMoreTableFooterDelegateMethods-
- -(void)loadMoreTableFooterDidTriggerRefresh:(LoadMoreTableFooterView*)view{
- if(self.loadMoreDataSourceFunc&&self.loadMoreDataSourceCompleted){
- self.loadMoreDataSourceFunc();
- doubledelayInSeconds=3.0;
- dispatch_time_tpopTime=dispatch_time(DISPATCH_TIME_NOW,delayInSeconds*NSEC_PER_SEC);
- dispatch_after(popTime,dispatch_get_main_queue(),
- self.loadMoreDataSourceCompleted);
- }
- }
- -(BOOL)loadMoreTableFooterDataSourceIsLoading:(LoadMoreTableFooterView*)view{
- returnself.isLoadingMore;
- }
- #pragmamark-EGORefreshTableHeaderDelegateMethods-
- -(void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView*)view{
- if(self.refreshDataSourceFunc&&self.refreshDataSourceCompleted){
- self.refreshDataSourceFunc();
- doubledelayInSeconds=3.0;
- dispatch_time_tpopTime=dispatch_time(DISPATCH_TIME_NOW,delayInSeconds*NSEC_PER_SEC);
- dispatch_after(popTime,dispatch_get_main_queue(),
- self.refreshDataSourceCompleted);
- }
- }
- -(BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView*)view{
- returnself.isRefreshing;
- }
- -(NSDate*)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView*)view{
- return[NSDatedate];
- }
- #pragmamark-UIScrollViewDelegateMethods-
- -(void)scrollViewWillBeginDecelerating:(UIScrollView*)scrollView{
- self.currentOffsetPoint=scrollView.contentOffset;
- }
- -(void)scrollViewDidScroll:(UIScrollView*)scrollView{
- CGPointpt=scrollView.contentOffset;
- if(self.currentOffsetPoint.y<pt.y){
- [self.loadMoreFooterViewloadMoreScrollViewDidScroll:scrollView];
- }else{
- [self.refreshHeaderViewegoRefreshScrollViewDidScroll:scrollView];
- }
- }
- -(void)scrollViewDidEndDragging:(UIScrollView*)scrollViewwillDecelerate:(BOOL)decelerate{
- CGPointpt=scrollView.contentOffset;
- if(self.currentOffsetPoint.y<pt.y){
- [self.loadMoreFooterViewloadMoreScrollViewDidEndDragging:scrollView];
- }else{
- [self.refreshHeaderViewegoRefreshScrollViewDidEndDragging:scrollView];
- }
- if(!decelerate&&self.loadImagesForVisiableRowsFunc){
- self.loadImagesForVisiableRowsFunc();
- }
- }
- -(void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView{
- if(self.loadImagesForVisiableRowsFunc){
- self.loadImagesForVisiableRowsFunc();
- }
- }
- #pragmamark-downloadimageasync-
- -(void)appImageDidLoad:(NSIndexPath*)indexPath{
- if(self.appImageDownloadCompleted){
- self.appImageDownloadCompleted(indexPath);
- }
- }
ELTableViewController 的使用
创建一个新的controller继承自:ELTableViewController;
override父类的initBlocks方法:
- #pragmamark-privatemethods-
- -(void)loadDataSource{
- self.dataSource=[NSMutableArrayarray];
- [self.dataSourceaddObject:@"dataSource_1"];
- [self.dataSourceaddObject:@"dataSource_2"];
- [self.dataSourceaddObject:@"dataSource_3"];
- [self.dataSourceaddObject:@"dataSource_4"];
- [self.dataSourceaddObject:@"dataSource_5"];
- [self.dataSourceaddObject:@"dataSource_6"];
- [self.dataSourceaddObject:@"dataSource_7"];
- [self.dataSourceaddObject:@"dataSource_8"];
- [self.dataSourceaddObject:@"dataSource_9"];
- [self.dataSourceaddObject:@"dataSource_10"];
- }
- -(void)initBlocks{
- __blockTestViewController*blockedSelf=self;
- //loadmore
- self.loadMoreDataSourceFunc=^{
- [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_1"];
- [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_2"];
- [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_3"];
- [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_4"];
- [blockedSelf.dataSourceaddObject:@"loadMoreDataSourceBlock_5"];
- blockedSelf.isLoadingMore=YES;
- [self.tableViewreloadData];
- NSLog(@"loadMoreDataSourceBlockwasinvoked");
- };
- //loadmorecompleted
- self.loadMoreDataSourceCompleted=^{
- blockedSelf.isLoadingMore=NO;
- [blockedSelf.loadMoreFooterViewloadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];
- NSLog(@"afterloadMorecompleted");
- };
- //refresh
- self.refreshDataSourceFunc=^{
- blockedSelf.dataSource=[NSMutableArrayarray];
- [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_1"];
- [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_2"];
- [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_3"];
- [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_4"];
- [blockedSelf.dataSourceaddObject:@"refreshDataSourceBlock_5"];
- blockedSelf.isRefreshing=YES;
- [self.tableViewreloadData];
- NSLog(@"refreshDataSourceBlockwasinvoked");
- };
- //refreshcompleted
- self.refreshDataSourceCompleted=^{
- blockedSelf.isRefreshing=NO;
- [blockedSelf.loadMoreFooterViewloadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];
- NSLog(@"afterrefreshcompleted");
- };
- self.cellForRowAtIndexPathDelegate=^(UITableView*tableView,NSIndexPath*indexPath){
- staticNSString*cellIdentifier=@"cellIdentifier";
- UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:cellIdentifier];
- if(!cell){
- cell=[[[UITableViewCellalloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:cellIdentifier]autorelease];
- }
- cell.textLabel.text=[blockedSelf.dataSourceobjectAtIndex:indexPath.row];
- NSLog(@"block:cellForRowAtIndexPathBlockhasbeeninvoked.");
- returncell;
- };
- self.heightForRowAtIndexPathDelegate=^(UITableView*tableView,NSIndexPath*indexPath){
- NSLog(@"block:heightForRowAtIndexPathBlockhasbeeninvoked.");
- return60.0f;
- };
- self.didSelectRowAtIndexPathDelegate=^(UITableView*tableView,NSIndexPath*indexPath){
- NSLog(@"block:didSelectRowAtIndexPathDelegatehasbeeninvoked.");
- };
- }
然后在ViewDidLoad中调用:
- [selfinitBlocks];
- [selfloadDataSource];
- [self.tableViewreloadData];
最后,你在实例化该controller的时候,可以指定是否使用上提和下拉
- 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
本文介绍了一种封装后的UITableView使用方式,通过内置下拉刷新、上提加载更多等功能,并利用Block提供自定义逻辑,大大简化了iOS列表开发流程。
1243

被折叠的 条评论
为什么被折叠?



