犹豫着是不是要将今天讲的"列表数据加载"也和整合到昨天写的"简单框架"上,又看了项目的本地缓存机制。觉得本地缓存好像有点问题,也有可能是自己没看仔细看吧,只缓存了第一页数据到UserDefault,而且没有进行读取(我们现在iOS版项目是参考了OSChina的Android版的缓存机制,整页整页的缓存数据,而且是缓存到本地disk)。感觉这个OSChina的iOS版好像有些地方不是很完善,担心自己后面越写越乱。于是决定干脆就写成一个单独的Demo吧,这样即使不联系上下文也可以看得懂。
看代码时学到了一点,在OC中,单例需要覆盖allocWithZone方法,因为alloc会调用allocWithZone,如果有人没有覆盖重写allocWithZone,那么当他直接调用allocWithZone来创建对象时得到的对象仍然不是单例的。
列表数据加载,主要由上拉加载更多和下拉在线更新组成。
上拉加载更多
这个还是比较简单的,就是判断服务器数据是否已经加载完成了,如果是的话列表最后一项显示"已加载全部数据",否则显示一个"加载更多"的按钮,单击就可以请求数据,进而刷新列表。这里OSChina写了一个自定义的LoadingCell(继承自UITableViewCell,包含了一个UILabel和UIActivityIndicatorView)。这个LoadingCell被一个单例对象所管理。不过我并没有按它上面写的来实现,因为我觉得将这个“加载更多”列表项写成单例的比较绕,试了一些时间后,还是打算自己写一个吧,这样自己比较清楚。
我这里定义了一个NSObject的子类LastCell,其中持有一个UITableViewCell对象,并向外开放了几个方法(normal,loading,loadingFinished)来允许外部对象操作UITableView的显示效果。
LastCell.h:
#import <Foundation/Foundation.h> //一个NSObject的子类,持有一个UITableViewCell的对象引用,并向外开放几个方法(normal,loading,loadingFinished)来控制当前的显示状态 @interface LastCell : NSObject @property (nonatomic,strong) IBOutlet UITableViewCell *cell; //不同的状态对应的不同文本描述 @property (nonatomic,strong) NSString *normalStr; @property (nonatomic,strong) NSString *loadingStr; @property (nonatomic,strong) NSString *loadingFinishedStr; //初始化方法,设置状态文本 -(id)initWithNormalStr:(NSString*)_normalStr andLoadingStr:(NSString*)_loadingStr andLoadingFinishedStr:(NSString*)_loadingFinishedStr; -(void)normal; //"加载更多" -(void)loadingFinished; //"数据已全部加载完成" -(void)loading; //"数据加载中" @endLastCell.m:
#import "LastCell.h" @implementation LastCell @synthesize cell; @synthesize loadingStr; @synthesize loadingFinishedStr; @synthesize normalStr; UILabel *label; UIActivityIndicatorView *indicator; -(id)initWithNormalStr:(NSString *)_normalStr andLoadingStr:(NSString *)_loadingStr andLoadingFinishedStr:(NSString *)_loadingFinishedStr { loadingFinishedStr = _loadingFinishedStr; loadingStr = _loadingStr; normalStr = _normalStr; cell = [[UITableViewCell alloc]initWithFrame:CGRectMake(0, 0, 320, 40)]; label = [[UILabel alloc]initWithFrame:CGRectMake(100, 0, 200, 20)]; indicator.frame = CGRectMake(0, 20, 20, 20); indicator.center = CGPointMake(200, 20); indicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; label.center = cell.center; [cell addSubview:label]; [cell addSubview:indicator]; [self normal]; return self; } //正在加载数据中状态 -(void)loading { [indicator startAnimating]; [label setText:loadingStr]; } //加载完全部数据状态 -(void)loadingFinished { [indicator stopAnimating]; [label setText:loadingFinishedStr]; } //加载更多状态 -(void)normal { [indicator stopAnimating]; [label setText:normalStr]; } @end
上面的逻辑相较于OSChina更简单一点,只需要将LastCell.cell添加到UITableView中,然后更加UITableView的状态来调用LastCell的normal,loading,loadingFinished等方法来控制LastCell.cell的显示即可。
下拉刷新数据
下拉刷新数据使用了第三方的一个组件EGORefreshTableHeaderView来实现,将EGORefreshTableHeaderView添加到UITableView上(这样它就可以随UITableView滚动而滚动了)。并实现EGORefreshTableHeaderDelegate中的几个方法:
//触发数据刷新事件,此时EGORe...View开始动画,我们可以在此时进行数据请求 - (void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView *)view { //开始请求数据,注意EGORe...View的动画,应该在数据请求完成后手动关闭,即调用egoRefreshScrollViewDataSourceDidFinishedLoading方法即可 [self reloadTableViewDataSource]; } //与最近更新时间有关的方法 - (NSDate *)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView *)view { return [NSDate date]; } //用来查询当前刷新状态的方法 - (BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView *)view { return _reloading; }再简单的介绍一下EGORefreshTableHeaderView的方法调用流程:
1.拖动列表带动EGORe...View,使其触发egoRefreshTableHeaderDidTriggerRefresh:方法,在该方法中,我们可以调用自己请求服务器数据的方法
2.在请求完服务器数据后,调用EGORe...View的egoRefreshScrollViewDataSourceDidFinishedLoading方法手动调用关闭EGORe...View的动画。
3、重载其他两个方法egoRefreshTableHeaderDataSourceLastUpdated和egoRefreshTableHeaderDataSourceIsLoading。
这里主界面MyViewController.h
#import <UIKit/UIKit.h> #import "EGORefreshTableHeaderView.h" @interface MyViewController : UIViewController <UITableViewDelegate,UITableViewDataSource,EGORefreshTableHeaderDelegate> { EGORefreshTableHeaderView *_refreshHeaderView; BOOL _reloading;//只是RefreshHeaderView的加载状态 } @property (nonatomic,strong) IBOutlet UITableView *tableView; @property (nonatomic,strong) NSMutableArray *tableData; //下拉刷新 - (void)reloadTableViewDataSource; - (void)doneLoadingTableViewData; @endMyViewController.m
#import "MyViewController.h" #import "LastCell.h" @interface MyViewController () @end @implementation MyViewController @synthesize tableData; @synthesize tableView; NSMutableArray *serverData; //模拟服务器数据 NSInteger currPage; //当前页数标记,从1开始 NSInteger pageSize; //每页包含的数据条数 BOOL isFinishedLoad; //表示是否已经加载完服务器上的数据,用来控制列表最后一项的显示 LastCell *lastCell; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; //初始化参数 isFinishedLoad = NO; pageSize = 20; currPage = 1; tableData = [[NSMutableArray alloc]initWithCapacity:20]; serverData = [[NSMutableArray alloc] initWithCapacity:20]; //初始化服务器模拟数据,添加随机数数据,便于在下拉刷新时加以区分 for(int i=0;i<36;i++) { [serverData insertObject:[NSString stringWithFormat:@"item%d,data:%d" ,i,((arc4random() % 100) + 1)] atIndex:i]; } //添加RefreshTableHeaderView到UITableView上,使他随UITableView一起滚动 if (_refreshHeaderView == nil) { EGORefreshTableHeaderView *view = [[EGORefreshTableHeaderView alloc] initWithFrame:CGRectMake(0.0f, -320.0f, self.view.frame.size.width, 320)]; view.delegate = self; [self.tableView addSubview:view]; _refreshHeaderView = view; } [_refreshHeaderView refreshLastUpdatedDate]; return self; } - (void)viewDidLoad { [super viewDidLoad]; tableView.delegate = self; tableView.dataSource = self; lastCell = [[LastCell alloc]initWithNormalStr:@"加载更多" andLoadingStr:@"数据加载中" andLoadingFinishedStr:@"数据已全部加载完成"]; //请求服务器数据 [self performSelector:@selector(getDataFromServe) withObject:nil afterDelay:0.2]; } /** * @brief * 根据当前数据页标记从服务器请求数据 */ -(void)getDataFromServe { [lastCell loading]; if([serverData count]>= currPage*pageSize) { //1.请求服务器数据 for(int i=(currPage-1)*pageSize;i<(currPage * pageSize);i++) { [tableData insertObject:[serverData objectAtIndex:i] atIndex:i]; } isFinishedLoad = NO; } else { //已加载全部服务器数据 //1.请求服务器数据 for(int i=(currPage-1)*pageSize;i<[serverData count];i++) { [tableData insertObject:[serverData objectAtIndex:i] atIndex:i]; } isFinishedLoad = YES; } currPage ++; //为了模拟网络请求的效果添加了1.2秒的延时效果 [self performSelector:@selector(hideForWhat) withObject:nil afterDelay:1.2]; } //使用performSelector添加延时,否则看不到IndicatorView的效果 -(void)hideForWhat { if(isFinishedLoad) { [lastCell loadingFinished]; } else { [lastCell normal]; } //2.刷新列表 [tableView reloadData]; [self doneLoadingTableViewData]; } #pragma mark - UITableView DataSource -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [tableData count] + 1; //这里的"+1"是针对列表最后一项"加载更多"/"已完成加载" } -(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if(indexPath.row < [tableData count]) { NSString *cellTag = @"cellTag"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellTag]; if(cell == nil) { cell = [[UITableViewCell alloc]init]; } UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(30, 0, 120, 40)]; [label setText:[tableData objectAtIndex:indexPath.row]]; [cell addSubview:label]; return cell; } else { //最后一项,加载更多 return lastCell.cell; } } #pragma mark - UITableView Delegate -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"didSelected"); if(indexPath.row >= [tableData count]) { if(!isFinishedLoad) { //判断,避免重复加载 [self performSelector:@selector(getDataFromServe) withObject:nil afterDelay:0.4]; } } } //完成下拉刷新动作,隐藏RefreshHeaderView - (void)doneLoadingTableViewData { _reloading = NO; [_refreshHeaderView egoRefreshScrollViewDataSourceDidFinishedLoading:self.tableView]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [_refreshHeaderView egoRefreshScrollViewDidScroll:scrollView]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [_refreshHeaderView egoRefreshScrollViewDidEndDragging:scrollView]; } -(void)reloadTableViewDataSource { _reloading = YES; [serverData removeAllObjects]; [tableData removeAllObjects]; //初始化服务器模拟数据,添加随机数数据,便于在下拉刷新时加以区分 for(int i=0;i<36;i++) { [serverData insertObject:[NSString stringWithFormat:@"item%d,data:%d" ,i,((arc4random() % 100) + 1)] atIndex:i]; } currPage = 1; [self getDataFromServe]; } //触发数据刷新事件,此时EGORe...View开始动画,我们可以在此时进行数据请求 - (void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView *)view { //开始请求数据,注意EGORe...View的动画,应该在数据请求完成后手动关闭,即调用egoRefreshScrollViewDataSourceDidFinishedLoading方法即可 [self reloadTableViewDataSource]; } //与最近更新时间有关的方法 - (NSDate *)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView *)view { return [NSDate date]; } //用来查询当前刷新状态的方法 - (BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView *)view { return _reloading; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end因为模拟了服务器数据,所以代码可能有点长。下面是运行效果
界面有些粗糙,但是基本实现了下拉刷新和上拉加载更多的功能
工程地址:http://download.csdn.net/detail/u011638883/6615227
O啦~~~
转载请求保留出处:http://blog.csdn.net/u011638883/article/details/16938409
谢谢!!