项目干货挖掘7——ViewController的分页数据管理

前言

在应用中有很多界面是列表页。一次加载不会加载出全部数据,而是只加载一部分(一段),或者说一页的数据,然后再往上拉便会加载出下一页的数据。
比如下面这个优惠券的界面,有三列分别显示三种状态的优惠券。只有一个优惠券列表的接口,传入不同的状态参数status,就返回相应状态的列表。
另外在项目中所有的列表类的接口,都要传入另外两个和分页有关的参数pageNopageSize,分别表示第几页和每页的数据条数。pageSize这个值一般都是固定不变的,看你一页显示10条数据或者20条数据。但是当想上拉加载下一页时,pageNo这个参数得+1。
另外因为对应3列数据,那我们是不是应该要维护3个NSMutableArray数组。当从后台收到响应数据时,我们得根据当前列表数据的状态类型status来处理相应的数组。

这样将会有很多逻辑判断在ViewController里,尤其是在网络数据的回调方法里。

WechatIMG1.png


数据分页管理器

我们写了个用于数据分页管理的类,项目中是列表分页的界面都用它来管理数据。

屏幕快照 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获得。
    另外,因为这是个列表接口,所以还有两个有关分页的参数pageNopageSize。而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];
}
  • 另外再切换顶部菜单时也要更新_segDataManagercurrentColumn属性。
    这个顶部菜单的点击事件是通过一个block回调出来的,并且将其处理封装成了一个方法。
        _segMenu.segMenuClickBlock = ^(NSInteger index){
            [weakSelf segMenuSwitchHandle:index];
        };

该方法内部就是点击顶部菜单后的处理逻辑:根据按钮的索引更新_segDataManagercurrentColumn属性,然后刷新数据。

- (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了。
若还暂无该列的缓存数据,则乖乖地去请求网络接口。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容