美文网首页iOS Developer小知识点好东西
项目干货挖掘7——ViewController的分页数据管理

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

作者: Wang66 | 来源:发表于2016-10-28 15:10 被阅读438次

    前言

    在应用中有很多界面是列表页。一次加载不会加载出全部数据,而是只加载一部分(一段),或者说一页的数据,然后再往上拉便会加载出下一页的数据。
    比如下面这个优惠券的界面,有三列分别显示三种状态的优惠券。只有一个优惠券列表的接口,传入不同的状态参数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了。
    若还暂无该列的缓存数据,则乖乖地去请求网络接口。


    相关文章

      网友评论

        本文标题:项目干货挖掘7——ViewController的分页数据管理

        本文链接:https://www.haomeiwen.com/subject/zhxeuttx.html