如何優(yōu)雅地動態(tài)插入數(shù)據(jù)到UITableView

任他風(fēng)吹雨打,我自巋然不動瘪吏!

當(dāng)我們實時往UITableView中插入數(shù)據(jù)并刷新列表的時候癣防,會發(fā)現(xiàn)列表是有抖動的。比如在微信聊天頁面掌眠,你滑動到某一個位置保持住蕾盯,然后收到一個或者若干人的微信(這幾個人不在當(dāng)前聊天列表中)。你會發(fā)現(xiàn)每收到一個人的信息蓝丙,列表向下沉级遭,就是有一個“抖動”的過程。當(dāng)然渺尘,并不是說微信體驗不好挫鸽,只是拋磚引玉。

言歸正傳鸥跟,我要討論的場景如下:

當(dāng)前列表展示了很多新聞丢郊,同時后臺在加載第三方廣告。廣告加載完成后需要按照規(guī)定的位置順序循環(huán)地插入到列表中,比如第5蚂夕,12迅诬,19,26...婿牍,要求插入廣告后當(dāng)前展示的頁面沒有下沉抖動現(xiàn)象,避免剛剛看的新聞跳到不可知的位置去了惩歉。

由于這里廣告不是直接附加在列表末尾等脂,也不是一次性插入到相鄰的位置,而是離散地分布在整個列表中撑蚌,所以不好用
insertRowsAtIndexPaths:withRowAnimation:或者
reloadRowsAtIndexPaths:withRowAnimation:局部刷新上遥,必須對整個列表ReloadData。顯然這會導(dǎo)致列表下沉抖動争涌,最壞的情況是當(dāng)前展示的整個頁面下沉粉楚,這對于新聞客戶端來說體驗很不好。

首先亮垫,我會想到scrollToRowAtIndexPath:atScrollPosition:animated:這個方法模软。在我刷新完整個列表之后,再將UITableView滾動到之前記錄的位置饮潦。大致思路看代碼:

//刷新列表之前找到當(dāng)前屏最頂部的新聞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];
    }
    
}

乍一看燃异,這種方法挺優(yōu)美的,也好像能達到我們的目的继蜡。但實際上還是有問題的回俐,問題出在visibleCells這個方法。先來看看這個方法的定義:

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

即返回當(dāng)前展示的可見cell數(shù)組稀并。
不過仅颇,這個方法并不是"眼見為實的",有時候我們?nèi)庋劭床坏降腸ell它卻認為是可見的碘举,或者只部分可見的它也會返回給我們的忘瓦。比如圖中網(wǎng)易新聞最上面的新聞 “...夫人鏡頭里的民國世相”就只見到一部分,如果用它來置頂也是會有下沉抖動問題的殴俱。

網(wǎng)易新聞截圖

那么還有沒有更優(yōu)雅的方式呢政冻?Absolutely!!!

既然用cell做單位來滾動太粗糙,我們可以用像素級別滾動來優(yōu)雅地保持置頂新聞巋然不動线欲。

首先我們要知道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.

關(guān)于ContentOffset明场、ContentSize、ContentInset的區(qū)別這里就不贅述了李丰,可以參考這里苦锨。

就是說ReloadData只刷新當(dāng)前屏幕可見的哪些cell,只會對visibleCells調(diào)用
tableView:cellForRowAtIndexPath:contentOffset是保持不變的舟舒,所以我們才看到了“抖動現(xiàn)象”拉庶,就像新聞被擠下去了。

contentOffset模擬圖

圖中灰色部分表示iPhone的屏幕秃励,粉紅色表示所有數(shù)據(jù)的布局大小氏仗,白色單元是隱藏在屏幕上方的數(shù)據(jù),綠色表示目標(biāo)廣告單于格夺鲜。

左圖的當(dāng)前屏幕最上面的新聞是news 11皆尔,UITableview的contentOffset是200,我們可以計算出news 11之前所有新聞單元格的高度總和得出現(xiàn)在news 11的偏移量preOffset币励。

右圖是在第三個位置插入一個廣告后的布局慷蠕。UITableview的contentOffset還是200,但是news 11被“擠下去”了食呻。我們同樣可以計算news 11之前所有新聞單元格和廣告單元格的高度總和得出現(xiàn)在news 11的偏移量afterOffset流炕。

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

deltaOffset = afterOffset - preOffset;

那么,為了保證news 11還是展示在當(dāng)初的位置仅胞,我們只要手動更新ContentOffset的值就可以了每辟,相當(dāng)于將粉紅色部分上移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對應(yīng)新聞的偏移量
- (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;
}

如此饼问,就可以真正做到當(dāng)前屏幕一點都不下沉了影兽。如果廣告插在當(dāng)前屏幕之外,用戶是感覺不到的莱革,等滑動列表才能在相應(yīng)位置看到廣告峻堰;如果插入到當(dāng)前屏幕中,用戶在課間區(qū)域看到插入一個新聞盅视,但是置頂?shù)男侣勎恢檬潜3植粍拥摹?/p>

盡享絲滑~

最后稍微提一下計算偏移量中用到的一個小技巧捐名。

如果所有的新聞和廣告單元的高度是固定的,那么heightForRowAtIndexPath:是很方便計算的闹击。如果是動態(tài)的镶蹋,就需要用到一點技巧了。

比如廣告的數(shù)據(jù)用AdModel表示赏半。為了讓廣告單元的高度隨廣告內(nèi)容動態(tài)調(diào)整贺归,我們一般習(xí)慣在AdModel里用一個cellHeight字段。

@interface AdModel:NSObject

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

@end

在我們填充內(nèi)容渲染廣告位的時候算出高度再賦值給cellHeight断箫。

在上面的場景下拂酣,前面雖然插入了廣告,但是ReloadData的時候仲义,UITableView并不會刷新不可見的廣告位婶熬,因此cellHeight始終為0剑勾,這就導(dǎo)致heightForRowAtIndexPath:不能計算出正確的結(jié)果。

巧妙地赵颅,我們在廣告插入self.dataArr的時候定義一個臨時的廣告單元變量AdCell虽另,并主動調(diào)用渲染的接口來給cellHeight賦值。

AdCell *tmpCell = [AdCell new];
[tmpCell setAdsContent:model];//這里會渲染廣告位并計算出cellHeight 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饺谬,一起剝皮案震驚了整個濱河市捂刺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌募寨,老刑警劉巖叠萍,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绪商,居然都是意外死亡,警方通過查閱死者的電腦和手機辅鲸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門格郁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人独悴,你說我怎么就攤上這事例书。” “怎么了刻炒?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵决采,是天一觀的道長。 經(jīng)常有香客問我坟奥,道長树瞭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任爱谁,我火速辦了婚禮晒喷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘访敌。我一直安慰自己凉敲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布寺旺。 她就那樣靜靜地躺著爷抓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阻塑。 梳的紋絲不亂的頭發(fā)上蓝撇,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音叮姑,去河邊找鬼唉地。 笑死据悔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的耘沼。 我是一名探鬼主播极颓,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼群嗤!你這毒婦竟也來了菠隆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狂秘,失蹤者是張志新(化名)和其女友劉穎骇径,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體者春,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡破衔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了钱烟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晰筛。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拴袭,靈堂內(nèi)的尸體忽然破棺而出读第,到底是詐尸還是另有隱情,我是刑警寧澤拥刻,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布怜瞒,位于F島的核電站,受9級特大地震影響般哼,放射性物質(zhì)發(fā)生泄漏吴汪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一逝她、第九天 我趴在偏房一處隱蔽的房頂上張望浇坐。 院中可真熱鬧,春花似錦黔宛、人聲如沸近刘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽觉渴。三九已至,卻和暖如春徽惋,著一層夾襖步出監(jiān)牢的瞬間案淋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工险绘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留踢京,地道東北人誉碴。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像瓣距,于是被迫代替她去往敵國和親黔帕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容