控制器中两个TableView的一些细节处理

先展示一张效果图

当一个控制器内有两个TableView时,它们共用一个代理和一些数据源方法,这时候就相当复杂。 右侧的TableView根据左侧的选中显示对应数据,两个TableView关联会产生很多复杂的共用问题,现在将这些问题和细节整理出来,方便自己查阅,以后避免,同时也给一同学习需要的朋友们。


###1.重复发送网络请求的处理办法 当点击了左侧的TableView将发送网络请求给服务器,右侧的TableView将返回的数据装载到右侧的Cell中,但是有时用户点了左侧的A cell又点击了B cell,再点击A Cell时会重复发送网络请求给服务器,这样即浪费了用户流量,而且网络不好时用户体验也非常差。

解决办法: 给左侧cell的模型增加一个成员属性,用来保存该类别对应的用户数据。 一下我们以YGRecommendCategory类为例,在YGRecommendCategory类的.h文件中增加该可变数组

/** 保存该类别对应的用户数据 */
@property (nonatomic, strong) NSMutableArray *users;
复制代码

在.m文件中重写users的getter方法,采用懒加载,不用关心该属性什么时候创建,只创建一次,提升系能

- (NSMutableArray *)users
{
    if (!_users) { // 如果users没用值,则创建,否则直接返回_users值
        _users = [NSMutableArray array];
    }
    return _users;
}
复制代码

在TableView控制器的代理方法- (void)tableView: didSelectRowAtIndexPath:中,将每次返回的用户数据存储到这个users大数组中,方便下次直接读取。因为每次点击Cell都会调用这个方法,所以在这个方法中将返回的数据保存起来

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 取出左边选中行对应的用户模型数据
    YGRecommendCategory *c = self.categories[indexPath.row];
    
    if (c.users.count) { // 如果users中有对应的值,直接加载数据,不再发送网络请求
        // 刷新右侧的TableView,显示最新的拿到的数据
        [self.rightTableView reloadData];
    } else {
        // 发送请求给服务器, 加载右侧的数据
        NSMutableDictionary *params = [NSMutableDictionary dictionary];
        params[@"a"] = @"list";
        params[@"c"] = @"subscribe";
        params[@"category_id"] = @(c.id);
        [[AFHTTPSessionManager manager] GET:@"http://api.budejie.com/api/api_open.php" parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
            // 将返回的字典数组转为模型数组
            NSArray *users = [YGRecommendUser mj_objectArrayWithKeyValuesArray:responseObject[@"list"]];
            
            // 将服务器返回的右侧cell数据添加到users这个可变大数组中方便下次直接读取
            [c.users addObjectsFromArray:users];
            
            // 刷新右边的表格
            [self.rightTableView reloadData];
        } failure:^(NSURLSessionDataTask *task, NSError *error) {
            YGLog(@"%@", error);
        }];
    }

}
复制代码

###2 加载多页数据 当右侧Cell返回数据较多时,一页显示不完,我们就需要让用户往上拖拽刷新,加载更多数据,刷新数据的方法自己写较为复杂,需要监听TableView的滚动、拖拽等,这里我采用MJRefresh的第三方框架来实现。 解决办法: 当控制器加载完毕后,首先在右侧控制器的.m文件中添加两个头尾刷新控件,同时写两个方法- (void)loadMoreUsers 和 - (void)loadNewUsers,用来实现往上拖拽和往下拖拽刷新需要做的操作

// 添加头部控件
self.rightTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewUsers)];
// 添加尾部刷新控件
self.rightTableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreUsers)];
// 默认设置footer控件隐藏,当拖拽时才出现    
self.rightTableView.mj_footer.hidden = YES;
复制代码

点击切换左侧Cell时,来决定右侧footer控件出现或隐藏,由于需要经常判断footer的状态,所以封装一个- (void)checkFooterState函数

- (void)checkFooterState
{
    // 取出左侧Cell选中的模型
    YGRecommendCategory *c = self.categories[self.leftTableView.indexPathForSelectedRow.row];
    
    // 每次刷新右边数据时, 都控制footer显示或者隐藏
    // 当users大数组中没有数据时,隐藏footer控件
    self.rightTableView.mj_footer.hidden = (c.users.count == 0);

    if (c.users.count == c.total) { // 全部数据加载完毕
        [self.rightTableView.mj_footer endRefreshingWithNoMoreData]; // 提示用户所有数据已经加载完毕
    } else { // 还没有加载完毕
        [self.rightTableView.mj_footer endRefreshing]; // 结束刷新,等待用户拖拽刷新
    }
}
复制代码

在- (NSInteger)tableView: numberOfRowsInSection:代理方法中监测footer的状态,用来判断显示或隐藏。因为每次点击左侧的Cell都会调用这个方法,在一开始就要判断右侧的footer显示怎样的状态。这个很重要!!!不然直接导致后面右侧数据的footer控件状态显示不正确

[self checkFooterStatus];
复制代码

在- (void)loadMoreUsers函数中实现发送网络请求的代码,同时将返回的数据保存到YGRecommendCategory类的users大数组中

// 添加到当前对应的用户数据中
[category.users addObjectsFromArray:user];
复制代码

然后添加检测底部控件状态代码

[self checkFooterStatus];
复制代码

在YGRecommendCategory类的.h文件中再添加两个成员属性用来保存服务器返回的右侧Cell的总数total和已经加载出来的当前页current_page

/**
 *  总数
 */
@property (assign, nonatomic) NSInteger total;
/**
 *  当前页码
 */
@property (assign, nonatomic) NSInteger current_page;
复制代码

然后在- (void)loadMoreUsers方法中将服务器返回的总数保存给total变量

c.total = [responseObject[@"total" integerValue]];
复制代码

在- (void)tableView: didSelectRowAtIndexPath:方法的else判断中(没有数据,需要发送网络请求),将当前页设置为1

c.current_page = 1; // 设置当前页码为1
复制代码

并添加服务器页码参数

params[@"page"] = @(c.current_page); //先默认请求服务器第一页
复制代码

这时,我们在- (void)loadMoreUsers方法中加载更多数据时,将当前页码++

params[@"page"] = @(++category.current_page); // 将当前页加1后发送给服务器加载更多数据
复制代码

添加检测底部控件代码

[self checkFooterStatus];
复制代码

但是有一个问题,用户可能会有这样一种操作:

######用户往上拖拽加载更多数据后,又往下拖拽加载最新数据,再往上拖拽加载更多数据。

这样在加载更多数据时就会重复加载已有数据,因为我是将获取到的数据扔到大数组users中,所以还需要在- (void)loadNewUsers方法中清除users数组中所有值,下次加载新数据时,重新开始获取。

[c.users removeAllObjects];
复制代码

其中c 和 category 都是YGRecommendCategory类new出的对象。

###3.网络延迟带来的一些细节问题

  • #####用户点击了左侧cell的A,在点击B后,如果网络延迟,在B的cell数据回来之前,界面还在显示A类的数据 解决办法: 在- (void)tableView: didSelectRowAtIndexPath:该方法的条件判断中,在else加入一行。如果users数组中没有数据,那么发送网络请求之前先刷新右侧的TableView
[self.rightTableView reloadData];
复制代码
  • #####当用点击左侧Cell时,右侧应该默认进入下拉刷新状态,这样用户体验会好,否则如果网络不好,数据没回来之前右侧TableView将是一篇空白 解决办法: 为右侧TableView集成头部刷新控件header,同时实现一个加载最新数据的方法- (void)loadNewUsers
// 添加头部控件
self.rightTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewUsers)];
复制代码

然后在- (void)tableView: didSelectRowAtIndexPath:方法的else判断中加进入往下拖拽刷新

[self.rightTableView.mj_header beginRefreshing];
复制代码

将发送网络请求的代码放在- (void)loadNewUsers方法中,一旦进入下来刷新,将自动调用该方法,发送网络请求 数据一旦回来,则需要结束header刷新

[self.rightTableView.mj_header endRefreshing];
复制代码

如果下拉刷新失败,同样也要在失败block中加入结束刷新代码。

  • #####用户点击了A类Cell后,header控件自动刷新完,将数据展示出来,用户往上拽,footer控件加载更多数据,但有时,其实一页已经展示完毕 解决办法: 当header刷新完数据,同时判断footer显示“加载更多数据”或“全部数据加载完毕”,节省用户流量和期望,所以在- (void)loadNewUsers方法中加入检查header控件状态的代码
[self checkFooterStatus];
复制代码
  • #####在网络不好的情况下,如果用户点了左侧的某个Cell发送了网络请求,但是数据回来之前,用户又点击左侧其他Cell,这样就会连发多个请求给服务器,这样当数据返回后,用户也不知道现在显示出来的数据是哪次点击的内容 解决办法: 给控制器增加一个成员属性params,用来保存每次服务器请求参数,将每次请求的参数字典赋值给params保存,如果之前发送后返回的参数和现在保存的params参数不符,则让之前的请求不要刷新右边的TableView,这样就能保证最后一次点击,展示出来的数据是最后一次,之前请求回来的数据不会干扰。 首先,增加控制器属性
/**
 *  请求参数
 */
@property (strong, nonatomic) NSMutableDictionary *params;
复制代码

然后将每次请求参数保存起来

self.params = params; //将参数赋值给成员属性保存
复制代码

然后在GET请求的成功block中增加判断,如果上次的params和现在保存的params不同,即说明现在返回的数据是之前发送的请求,则不予刷新右边TableView,直接return。但是要将返回的数据保存起来供以后使用。

//如果两次请求不同,直接return
if (self.params != params) return;
复制代码
  • #####发送网络请求的GET方法调用非常频繁,如果直接利用[AFHTTPSessionManager manager]拿到当前manager会非常消耗内存。 解决办法:给控制增加一个manager的成员属性,用来保存当前manager,重写getter方法采用懒加载,保证每次发请求都是这个manager,这样只需创建一次。 控制器增加属性
/**
 *  AFHTTPSessionManager的manager
 */
@property (strong, nonatomic) AFHTTPSessionManager *manager;
复制代码

然后将代码中所有的[AFHTTPSessionManager manager] 替换成self.manager即可。

  • #####网络差时,用户点击了左侧Cell,数据还没返回时,用户失去耐心,停止等待,直接点击左上角返回,销毁控制器。这样当数据返回后,会调用self的成员属性,但发现控制器已经销毁,所以程序会奔溃 解决办法: manager对象有一个属性operationQueue,用来保存所有请求任务,拿到这个队列,调cancelAllOperations方法销毁所有任务。
// 重写控制器dealloc方法
-(void)dealloc
{
    //停止所有请求
    [self.manager.operationQueue cancelAllOperations];
}
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值