前言
在应用中有很多界面是列表页。一次加载不会加载出全部数据,而是只加载一部分(一段),或者说一页的数据,然后再往上拉便会加载出下一页的数据。
比如下面这个优惠券的界面,有三列分别显示三种状态的优惠券。只有一个优惠券列表的接口,传入不同的状态参数status
,就返回相应状态的列表。
另外在项目中所有的列表类的接口,都要传入另外两个和分页有关的参数pageNo
和pageSize
,分别表示第几页和每页的数据条数。pageSize
这个值一般都是固定不变的,看你一页显示10条数据或者20条数据。但是当想上拉加载下一页时,pageNo
这个参数得+1。
另外因为对应3列数据,那我们是不是应该要维护3个NSMutableArray
数组。当从后台收到响应数据时,我们得根据当前列表数据的状态类型status
来处理相应的数组。
这样将会有很多逻辑判断在ViewController
里,尤其是在网络数据的回调方法里。
数据分页管理器
我们写了个用于数据分页管理的类,项目中是列表分页的界面都用它来管理数据。
屏幕快照 2016-10-28 下午2.10.22.png先看头文件的代码:
@interface SegDataManager : NSObject
@property (nonatomic, strong)NSMutableArray *array;
@property (nonatomic, assign)NSInteger currentColumn; // 当前所在列
@property (nonatomic, assign)NSInteger columnCount; // 总列数
- (instancetype)initWithColumn:(NSInteger)column;
#pragma mark - segmentTotal
/*
设置当前段的数据总数
*/
- (void)setSegmentTotal:(NSInteger)total;
/*
获取当前段的数据总数
*/
- (NSInteger)segmentTotal;
#pragma mark - segmentData
/*
获取当前段的数据
*/
- (NSArray *)segmentData;
/*
存储当前段的数据
*/
- (void)storeSegmentData:(NSArray *)data;
/*
存储某段对应的数据
*/
- (void)storeSegmentData:(NSArray *)data segment:(NSInteger)segment;
#pragma mark - update page
/*
更新当前段的页数,+1或-1页
*/
- (void)updateSegmentPage:(NSInteger)num;
/*
页数重置,变为1
*/
//- (void)resetSegmentPage:(NSInteger)num;
- (void)resetSegmentPage;
#pragma mark - get page num
/*
获取当前的页码
*/
-(NSInteger)segPageNum;
/*
获取当前的页码
*/
-(NSString*)segPageText;
@end
#import "SegDataManager.h"
@interface SegDataManager ()
{
}
@end
@implementation SegDataManager
#pragma mark - init
- (instancetype)init
{
self = [super init];
if(self)
{
_columnCount = 1;
_currentColumn = 0;
}
return self;
}
- (instancetype)initWithColumn:(NSInteger)column
{
self = [self init];
if(self)
{
_columnCount = column;
_array = [NSMutableArray arrayWithCapacity:column];
for(int i=0; i<_columnCount; i++)
{
SegDataObject *segDataObj = [[SegDataObject alloc] init];
segDataObj.tag = i;
[_array addObject:segDataObj];
}
}
return self;
}
#pragma mark - segmentTotal
/*
设置当前段的数据总数
*/
- (void)setSegmentTotal:(NSInteger)total
{
SegDataObject *segDataObj = _array[_currentColumn];
segDataObj.total = total;
}
/*
获取当前段的数据总数
*/
- (NSInteger)segmentTotal
{
SegDataObject *segDataObj = _array[_currentColumn];
return segDataObj.total;
}
#pragma mark - segmentData
/*
获取当前段的数据
*/
- (NSArray *)segmentData
{
SegDataObject *segDataObj = _array[_currentColumn];
return segDataObj.dataArray;
}
/*
存储当前段的数据
*/
- (void)storeSegmentData:(NSArray *)data
{
if(!data||data.count==0){
return;
}
SegDataObject *segDataObj = _array[_currentColumn];
if(segDataObj.page==1){
[segDataObj.dataArray removeAllObjects];
}
[segDataObj.dataArray addObjectsFromArray:data];
if(data.count<segDataObj.pageNumber){
segDataObj.total = segDataObj.dataArray.count;
}
}
/*
存储某段对应的数据
*/
- (void)storeSegmentData:(NSArray *)data segment:(NSInteger)segment
{
if(segment<0||!data||data.count==0){
return;
}
SegDataObject *segDataObj = _array[segment];
if(segDataObj.page==1){
[segDataObj.dataArray removeAllObjects];
}
[segDataObj.dataArray addObjectsFromArray:data];
if(data.count<segDataObj.pageNumber){
segDataObj.total = segDataObj.dataArray.count;
}
}
#pragma mark - update page
/*
更新当前段的页数,+1或-1页
*/
- (void)updateSegmentPage:(NSInteger)num
{
SegDataObject *segDataObj = _array[_currentColumn];
segDataObj.page = segDataObj.page+num;
if(segDataObj.page<1){
segDataObj.page = 1;
}
}
/*
页数重置,变为1
*/
- (void)resetSegmentPage
{
SegDataObject *segDataObj = _array[_currentColumn];
segDataObj.page = 1;
[segDataObj.dataArray removeAllObjects];
}
#pragma mark - get page num
/*
获取当前的页数
*/
-(NSInteger)segPageNum
{
SegDataObject *segDataObj = _array[_currentColumn];
return segDataObj.page;
}
/*
获取当前的页数
*/
-(NSString*)segPageText
{
return [NSString stringWithFormat:@"%d", (int)[self segPageNum]];
}
@end
这个东西其实就是在内部维护一个表示数据源的数组属性array
,它的元素是SegDataObject
类型的实例对象。若有1列,该数组就有1个该类型对象的元素。若有3列,该数组就有个3个该类型对象的元素。
SegDataObject
表示每段(每页)的数据信息,包括数据本身信息及所在页码page
,该页数据总数total
等其他信息。SegDataObject
类的代码如下:
头文件:
@interface SegDataObject : NSObject
@property(nonatomic, strong)NSMutableArray *dataArray;
@property(nonatomic, assign)NSInteger tag; // 所在页码
@property(nonatomic, assign)NSInteger page; // 所在页码
@property(nonatomic, assign)NSInteger total; // 该页数据总数
@property(nonatomic, assign)NSInteger pageNumber; // 每页最大数量
@end
实现文件里没啥东西,唯一的东西就是重写了初始化方法,使其在初始化时自动初始化了一些属性的值。
@implementation SegDataObject
- (instancetype)init
{
self = [super init];
if(self)
{
_tag = 0;
_page = 1;
_total = 0;
// _pageNumber =
_dataArray = [[NSMutableArray alloc] init];
}
return self;
}
@end
使用
我们来看看在VC中我们该怎么使用它。
- 首先得初始化,传入参数3表示有3列数据:
_segDataManager = [[SegDataManager alloc] initWithColumn:3];
- 然后我们请求这个优惠券列表的接口请求。该接口需要传入
status
参数表示优惠券状态类型,而这个状态时需要用_segDataManager.currentColumn
获得。
另外,因为这是个列表接口,所以还有两个有关分页的参数pageNo
和pageSize
。而pageNo
需要通过[_segDataManager segPageText]
方法获得。
- (void)requestMyCouponsList_OneType
{
NSString *statusStr = nil;
if(_segDataManager.currentColumn == 0) statusStr = @"1";
else if(_segDataManager.currentColumn == 1) statusStr = @"3";
else if(_segDataManager.currentColumn == 2) statusStr = @"2";
NSDictionary * parameters=[[NSDictionary alloc] initWithObjectsAndKeys:
[_segDataManager segPageText],@"pageNo",
PageSize,@"pageSize",
statusStr,@"status",
nil];
[[RENetworkController shareNetworkController] sendRequestWithID:RequestMyCouponList
parameters:parameters
CallBackDelegate:self
httpType:http_get
RemoveAllRequest:NO];
}
- 然后是在网络请求的回调方法里对数据的处理。这块的逻辑有些复杂啰嗦,每个列表界面的回调方法里都要写这样一大段代码。虽然觉得不是很好,但是我不知道这儿是不是有更优雅的解决方案,还希望知道的朋友多多交流分享呐。
if(interface == RequestMyCouponList) //
{
if(result.status == 200)
{
NSArray *dataArr = [CouponObject ParseCouponJSONInfo:result.resultInfo];
[_segDataManager setSegmentTotal:[result.dataTotal integerValue]];
if (dataArr.count>0)
{
/*
存储该页的数据,并且将其赋给本界面的数据源_tableViewData;
并且当_tableViewData的数据个数等于segmentTotal时,就说明已经滑到最后一页了,所以要隐藏了显示“正在加载更多”之类的footer.
*/
[_segDataManager storeSegmentData:dataArr];
[_tableViewData removeAllObjects];
[_tableViewData addObjectsFromArray:[_segDataManager segmentData]];
if (_tableViewData.count>=[_segDataManager segmentTotal]){
_tableView.footer.hidden = YES;
}
}
else
{
// 返回的数据为0个,则显示空白界面
[_tableViewData removeAllObjects];
_tableView.footer.hidden = YES;
[self showNetworkRequestTips:NetworkTipsNull];
}
}
else
{
/*
若请求失败,则首先将当前页码回退1页,因为在加载更多触发时把页码+1后再去请求接口的。现在接口请求失败了,所以得回退。
并且显示原有数据,最后可能还得根据响应状态码做相应的处理(弹出提示等)
*/
[_segDataManager updateSegmentPage:-1];
[_tableViewData removeAllObjects];
[_tableViewData addObjectsFromArray:[_segDataManager segmentData]];
[self requestFailedCode:result currData:_tableViewData];
}
[_tableView reloadData];
}
- 接下来就是加载更多和刷新处,加载更多时将页码+1后再去请求接口,刷新时将页码重置为1后再去请求接口。
- (void)pullRefresh
{
if ([_tableView.footer isHidden]) {
_tableView.footer.hidden = NO;
}
[_segDataManager resetSegmentPage]; // 将页码重置为1
[self requestMyCouponsList_OneType];
}
- (void)loadMore
{
[_segDataManager updateSegmentPage:1]; // 更新页码,这儿是+1页
[self requestMyCouponsList_OneType];
}
- 另外再切换顶部菜单时也要更新
_segDataManager
的currentColumn
属性。
这个顶部菜单的点击事件是通过一个block回调出来的,并且将其处理封装成了一个方法。
_segMenu.segMenuClickBlock = ^(NSInteger index){
[weakSelf segMenuSwitchHandle:index];
};
该方法内部就是点击顶部菜单后的处理逻辑:根据按钮的索引更新_segDataManager
的currentColumn
属性,然后刷新数据。
- (void)segMenuSwitchHandle:(NSInteger)index
{
_segDataManager.currentColumn = index;
[self pullRefresh];
}
上面这种写法基本实现了功能,但是仔细想想还是不妥当。因为这样实现的话,每次切换顶部菜单按钮时都立马会去请求网络接口,这样不太优雅。我们应当考虑考虑缓存:
- (void)segMenuSwitchHandle:(NSInteger)index
{
_segDataManager.currentColumn = index;
NSArray *data=[[NSArray alloc]initWithArray:[_segDataManager segmentData]];
if (data.count>0)
{
[self hideNetworkStauts]; // 隐藏加载菊花/空白界面等
[_tableViewData removeAllObjects];
[_tableViewData addObjectsFromArray:data];
[_tableView reloadData];
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
if (_tableViewData.count>=[_segDataManager segmentTotal]){
_tableView.footer.hidden = YES;
}
else{
_tableView.footer.hidden = NO;
}
}
else
{
[self pullRefresh];
}
}
先根据按钮索引更新currentColumn
,再因此从_segDataManager
中得到该列下缓存的数据。
若有缓存数据,则先把正在加载的菊花和空白界面等东西先隐藏。然后将这些缓存数据data
装配给本界面的数据源_tableViewData
就OK了。
若还暂无该列的缓存数据,则乖乖地去请求网络接口。
网友评论