犹豫着是不是要将今天讲的"列表数据加载"也和整合到昨天写的"简单框架"上,又看了项目的本地缓存机制。觉得本地缓存好像有点问题,也有可能是自己没看仔细看吧,只缓存了第一页数据到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; //"数据加载中"
@end
LastCell.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;
@end
MyViewController.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
谢谢!!