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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容