转载

iOS之UITableView如何优雅的插入数据: 旋转180°下拉加载cell顶置

一. 概述

一般项目里面用到UITableView的概率还是灰常大的, 我的项目从一开始也用了. 大概的来说就是类似一个收件箱的功能, 推送来一条消息就加一个cell, 这是很简单的. 蓝鹅, 接下来经历了两次需求更改:

  • 1. 增加'数据本地化'的功能;

  • 2. 增加查看历史消息功能, 也就是下拉加载更多的消息.

第一个还好说, 就是用FMDB弄了个简单的数据库, 差不多就是获取数据的途径变成从数据库获取而已;

在增加了第一个需求之后, 叠加提出第二个需求, 看起来也很简单哦, 不就是加数据嘛. 但是, 当我开始做起来的时候才发现, 坑蛮多的.

这篇文章是基于我自己项目所写的, 可能在一些地方处理比较特殊并不具备"普世价值"; 但从开发者学习的角度来说, 我始终认为思路比代码重要. 所以这里我可能在思路方面比较啰嗦点, 直接要代码的慎重选择啊.

先说一下, 需求二里面主要遇到的坑有两个:

  • 坑一 : 下拉加载的10条消息, 显示直接在第0-4条, 要看第5-9条的话又得反方向上滑

为了解决坑一, 将tableview和cell都旋转了180°, 这时候, 坑二来了

  • 坑二 : 在初始cell数量较少的时候, 推送新消息过来增加cell的时候导致界面跳动

二. 项目过程

2.1需求分析

由于第一个需求太过简单, 所以就忽略啦. 这里直接讲第二个需求的.

刚刚上面讲的不是很清楚, 这里补充一下第二个需求的具体:

  • 1.进入到界面的时候, 里面要先显示最多10条消息;

  • 2.下拉可以加载历史数据(每次10条);

  • 3.当有新消息推送过来的时候, 要添加在最底部, 并滚动到最底部;

  • 4.三日内做完.

刚看到这个需求的时候, 我的第一个反应是这太TM简单了, too simple! 看老夫如何在三天时间以内用半天做完再用两天半假装没做(tou)完(lan).

2.2 坑一

蓝鹅, 忙活半天过后, 我发现自己too young too naive????. 这就是上面说的坑一, 图是这样的, 可以看到点击"消息+1"的时候还算正常, 但是下拉加载更多消息(10条)的时候, 都是停留在最顶部, 也就是上面坑一说的, 要看第5-9条的时候, 得反方向上滑

iOS之UITableView如何优雅的插入数据: 旋转180°下拉加载cell顶置

正常的.gif

为了解决坑一, 我尝试过调试NSIndexPath并且使用scrollToRowAtIndexPath:<#(nonnull nsindexpath=""> atScrollPosition:<#(uitableviewscrollposition)#> animated:<#(bool)#>]方法来各种折腾....再过了半天之后????放弃了, 下班, 对, 下班!!!谁说程序猿都得加班的.

2.3 坑二

昨晚想了一(yi)下(晚)和简单的实践(说好的不加班呢, 变相加班????!), 发现在uitableview的底部使用inset...和scroll...方法拼接数据很完美, 脑洞大开," 独创"(后来发现网上也有)ios大法之乾坤大挪移----将整个tableview旋转180°!!!!! 当然cell也要旋转180°.

旋转的代码分别是:

    CGAffineTransform transform =CGAffineTransformMakeRotation(M_PI);
    [tbV setTransform:transform];

cell的旋转:

        CGAffineTransform transform =CGAffineTransformMakeRotation(M_PI);
        [self.contentView setTransform:transform];

当然, 使用mjrefresh的话, 就得用原来使用的mj_header换成mj_footer了(关于mjrefresh的使用).

弄好之后是这样的, cell直接在最底部:

iOS之UITableView如何优雅的插入数据: 旋转180°下拉加载cell顶置

倒过来一.gif

这明显是不行的, 为了使得cell数量比较少的时候可以顶置(实际是底置, 因为tableview旋转了180°了), 于是想到了根据tableView.contentSize.height(关于 tableView.contentSize你想了解的)来设置tableView.contentInset, 代码是这样的

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    if (tableView.contentSize.height < tableView.frame.size.height)
    {
        
        tableView.contentInset = UIEdgeInsetsMake(tableView.frame.size.height - tableView.contentSize.height - RSRealValue(120), 0, 0, 0);
    }
}

运行出来一看, 哇, 完美解决!!!! 蓝鹅, 高兴地太早了. 这时候下拉加载时没问题的, 但新加一条消息的时候, 新的cell会把原来的cell顶上去, 而不是拼接在最下面, 如图:

iOS之UITableView如何优雅的插入数据: 旋转180°下拉加载cell顶置

倒过来contentInset.gif

经过一番思考, 本人大致的认为, 在新加入cell的时候, 原来的tableView.contentSize还是没变, 也就是说tableView.contentSize的布局其实还是只到了上一个cell的底部, 并以此往复, 所以出现了上图里面加了3条新消息再下滑的时候才出现.

这时候, 又想到了一个方法, 可以尝试监听tableView.contentSize还动态修改tableView.contentSize.....&%$#@!@#$%&%$过程省略几百字, 直接说结果吧, 然并卵. 而且, 在ios的UITableView里面, 并不是很推荐修改tableView.contentSize, 因为这个是苹果自身维持动态平衡的, 我们强心修改的话会影响其平衡, 而且在控制台会出现警报. 所以很不推荐.

就这样又折腾了半天.....下午整个人都在想这个问题. 终于在吃了一颗糖果之后, 想到一个方法:

  • 为啥不通过记录cell的总高度来代替tableView.contentSize, 然后设置一个透明的tableheaderview来代替设置tableView.contentInset快被自己的机智感动了. 

这样既可以绕过上面说的问题了. 说干就干.

需要在cell的model文件里面增加一个记录本cell高度的属性:

// 计算出来的cell的高度
@property (nonatomic, assign) CGFloat cellHeight;

同时, 也得在控制器文件里面增加一个记录所有cell总高度的属性:

// cell的叠加高度
@property (nonatomic, assign) CGFloat cellsTotalHeight;

并且写了它的懒加载方法, 这样每次在获取总高度的时候都能保证是最新的

- (CGFloat)cellsTotalHeight{
    
    CGFloat heightAll = 0;
    for (int i = 0; i < self.dataMulArr.count; i ++) {
        
        OneModel *model = self.dataMulArr[i];
        
        heightAll = heightAll + model.cellHeight;
    }
    
    _cellsTotalHeight = heightAll;
    
    NSLog(@"cell总高度: %f",heightAll);
    
    return heightAll;
}

同时, 返回头部视图和高度是这样de(为了方便调试, 先把头部视图设置成有个半透明色)

#pragma mark- 返回头部视图
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
    UIView *headV = [[UIView alloc] init];
//    headV.backgroundColor = [UIColor clearColor];
    headV.backgroundColor = RSColorFromRGBA(0x00FF00, 0.5);
    return headV;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    
    NSLog(@"头部视图高度中cell: %f", self.cellsTotalHeight);
    
    CGFloat headHeight = 0;
    
    if (self.cellsTotalHeight < self.tbV.frame.size.height)
    {
        
        headHeight = self.tbV.frame.size.height - self.cellsTotalHeight - RSRealValue(240);
    }
    return headHeight;
    
}

跑出来是这样的:

iOS之UITableView如何优雅的插入数据: 旋转180°下拉加载cell顶置

倒过来加header.gif

这时候再把tableheaderview的背景色改成透明的, 就成了这样了:

iOS之UITableView如何优雅的插入数据: 旋转180°下拉加载cell顶置

倒过来加header透明.gif

果然完美解决了, 这时候其实只剩一天时间假装没完(偷)成(lan)了.

不过还是要注意一下, 看图倒过来加header.gif将tableheaderview改成透明之后, 新增cell还是会被tableheaderview挡住, 所以最后一个cell是点击不到的. 因为我项目里面的cell不需要点击, 所以这个问题我可以忽略; 如果小伙伴项目里面的cell需要点击的话, 就要将tableheaderview高度进一步减去新增cell的高度哦. 这里我就不做具体代码了, 小伙伴们自己探索一下把.

三. 小总结

总结起来, 这里面几个关键点是:

  • 1.旋转180°

  • 2.不使用reloaddata方法, 而是使用inser....和scroll...两个方法

  • 3.通过增加tableheaderview来实现倒转tableview之后cell的顶置, 并且动态的改变tableheaderview的高度.

这个虽然只是项目的一小部分, 但每次解决问题之后都能获得成就感, 这就是码农单纯的快乐吧.

正文到此结束
Loading...