如何优雅地动态插入数据到UITableView

任他风吹雨打,我自岿然不动!

当我们实时往UITableView中插入数据并刷新列表的时候,会发现列表是有抖动的。比如在微信聊天页面,你滑动到某一个位置保持住,然后收到一个或者若干人的微信(这几个人不在当前聊天列表中)。你会发现每收到一个人的信息,列表向下沉,就是有一个“抖动”的过程。当然,并不是说微信体验不好,只是抛砖引玉。

言归正传,我要讨论的场景如下:

当前列表展示了很多新闻,同时后台在加载第三方广告。广告加载完成后需要按照规定的位置顺序循环地插入到列表中,比如第5,12,19,26...,要求插入广告后当前展示的页面没有下沉抖动现象,避免刚刚看的新闻跳到不可知的位置去了。

由于这里广告不是直接附加在列表末尾,也不是一次性插入到相邻的位置,而是离散地分布在整个列表中,所以不好用
insertRowsAtIndexPaths:withRowAnimation:或者
reloadRowsAtIndexPaths:withRowAnimation:局部刷新,必须对整个列表ReloadData。显然这会导致列表下沉抖动,最坏的情况是当前展示的整个页面下沉,这对于新闻客户端来说体验很不好。

首先,我会想到scrollToRowAtIndexPath:atScrollPosition:animated:这个方法。在我刷新完整个列表之后,再将UITableView滚动到之前记录的位置。大致思路看代码:

//刷新列表之前找到当前屏最顶部的新闻Id
- (NSString *)topNewsId {
    NSArray *visibleCells = [self.tableView visibleCells];
    
    UITableViewCell *cell = [visibleCells firstObject];
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    NewsModel *topNews = [self.dataArr objectAtIndex:indexPath.row];

    NSString *newsId = = topNews.newsId;
    return newsId;
}
//刷新之后再将之前顶部的新闻滚动到顶部 避免页面抖动
- (void)keepTopNews:(NSString *)topNewsId {
    int topNewsRow = 0;
    for (int i = 0; i <[self.dataArr count] ; i ++) {
        id data = [self.dataArr objectAtIndex:i];
        if ([data isKindOfClass:[NewsModel class]]) {
            NewsModel *model = data;
            if ([model.newsId isEqualToString:topNewsId]) {
                topNewsRow = i;
                break;
            }
        }
    }
    if (topNewsRow) {
        NSIndexPath *toIndex = [NSIndexPath indexPathForRow:topNewsRow inSection:0];
        [self.tableView scrollToRowAtIndexPath:toIndex atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }
    
}

乍一看,这种方法挺优美的,也好像能达到我们的目的。但实际上还是有问题的,问题出在visibleCells这个方法。先来看看这个方法的定义:

Returns an array of visible cells currently displayed by the collection view.

即返回当前展示的可见cell数组。
不过,这个方法并不是"眼见为实的",有时候我们肉眼看不到的cell它却认为是可见的,或者只部分可见的它也会返回给我们的。比如图中网易新闻最上面的新闻 “...夫人镜头里的民国世相”就只见到一部分,如果用它来置顶也是会有下沉抖动问题的。

网易新闻截图

那么还有没有更优雅的方式呢?Absolutely!!!

既然用cell做单位来滚动太粗糙,我们可以用像素级别滚动来优雅地保持置顶新闻岿然不动。

首先我们要知道ReloadData的一个特性:

When you call this method, the collection view discards any currently visible items and views and redisplays them. For efficiency, the collection view displays only the items and supplementary views that are visible after reloading the data. If the collection view’s size changes as a result of reloading the data, the collection view adjusts its scrolling offsets accordingly.

关于ContentOffset、ContentSize、ContentInset的区别这里就不赘述了,可以参考这里

就是说ReloadData只刷新当前屏幕可见的哪些cell,只会对visibleCells调用
tableView:cellForRowAtIndexPath:contentOffset是保持不变的,所以我们才看到了“抖动现象”,就像新闻被挤下去了。

contentOffset模拟图

图中灰色部分表示iPhone的屏幕,粉红色表示所有数据的布局大小,白色单元是隐藏在屏幕上方的数据,绿色表示目标广告单于格。

左图的当前屏幕最上面的新闻是news 11,UITableview的contentOffset是200,我们可以计算出news 11之前所有新闻单元格的高度总和得出现在news 11的偏移量preOffset。

右图是在第三个位置插入一个广告后的布局。UITableview的contentOffset还是200,但是news 11被“挤下去”了。我们同样可以计算news 11之前所有新闻单元格和广告单元格的高度总和得出现在news 11的偏移量afterOffset。

有了preOffset和afterOffset之后就可以知道news 11被“挤下去”多少距离

deltaOffset = afterOffset - preOffset;

那么,为了保证news 11还是展示在当初的位置,我们只要手动更新ContentOffset的值就可以了,相当于将粉红色部分上移deltaOffset的距离。

看代码:

- (void)insertAds:(NSArray *)ads {
    NSString *topNewsId = [self topNewsId];
    
    CGFloat preOffset = [self offSetOfTopNews:topNewsId];
    
    /*
    插入广告...
    */
    
    [self.tableView reloadData];

    CGFloat afterOffset = [self offSetOfTopNews:topNewsId];
    
    CGFloat deltaOffset = afterOffset - preOffset;
    
    CGPoint contentOffet = [self.tableView contentOffset];
    contentOffet.y += deltaOffset;
    self.tableView .contentOffset = contentOffet;
}

//计算newsId对应新闻的偏移量
- (CGFloat)offSetOfTopNews:(NSString *)newsId {
    CGFloat offset = 0;
    for (int i = 0; i < [self.dataArr count]; i ++) {
        id data = [self.dataArr objectAtIndex:i];
        if ([data isKindOfClass:[NewsModel class]]) {
            NewsModel *model = data;
            if ([model.newsId isEqualToString:newsId]) {
                break;
            }
        }
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        CGFloat height = [self heightForRowAtIndexPath:indexPath];
        offset += height;
    }
    return offset;
}

如此,就可以真正做到当前屏幕一点都不下沉了。如果广告插在当前屏幕之外,用户是感觉不到的,等滑动列表才能在相应位置看到广告;如果插入到当前屏幕中,用户在课间区域看到插入一个新闻,但是置顶的新闻位置是保持不动的。

尽享丝滑~

最后稍微提一下计算偏移量中用到的一个小技巧。

如果所有的新闻和广告单元的高度是固定的,那么heightForRowAtIndexPath:是很方便计算的。如果是动态的,就需要用到一点技巧了。

比如广告的数据用AdModel表示。为了让广告单元的高度随广告内容动态调整,我们一般习惯在AdModel里用一个cellHeight字段。

@interface AdModel:NSObject

@property (nonatomic, assign) NSInteger adId;
...
@property (nonatomic, assign) CGFloat   cellHeight;

@end

在我们填充内容渲染广告位的时候算出高度再赋值给cellHeight

在上面的场景下,前面虽然插入了广告,但是ReloadData的时候,UITableView并不会刷新不可见的广告位,因此cellHeight始终为0,这就导致heightForRowAtIndexPath:不能计算出正确的结果。

巧妙地,我们在广告插入self.dataArr的时候定义一个临时的广告单元变量AdCell,并主动调用渲染的接口来给cellHeight赋值。

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

推荐阅读更多精彩内容