iOS開發(fā)之多種Cell高度自適應(yīng)實現(xiàn)方案的UI流暢度分析(轉(zhuǎn)發(fā))

一玄括、總述

本篇博客主要給出了5種Cell自適應(yīng)高度的解決方案分瘦,并對比了每種實現(xiàn)方案的流暢度拇砰。也可以說是從UI最不流暢的一種我們慢慢優(yōu)化,從而實現(xiàn)了這5種解決方案匕垫。當(dāng)然我們是觀察屏幕的FPS來判斷屏幕在操作時是否卡頓僧鲁。關(guān)于對FPS的實時監(jiān)測,我參考了YYKit-Demo中的做法象泵,并將其單獨(dú)提取了一個組件寞秃,便于我們項目的使用,關(guān)于這個提取的FPS組件偶惠,下方使用時會具體介紹春寿。當(dāng)然本篇博客所涉及的所有代碼,依然會分享到Github上忽孽,文章后方會給出相應(yīng)的鏈接绑改,有需要的小伙伴請自行clone。

下方這個截圖是我們今天demo的菜單列表頁面兄一,點擊每個Cell都會跳轉(zhuǎn)左邊這個內(nèi)容列表頁面厘线。不過每個Cell所對應(yīng)的內(nèi)容頁面的Cell自適應(yīng)高度的實現(xiàn)方式不同,我們在對其滑動操作時出革,可以根據(jù)下方這個FPS組件來觀察屏幕的流暢度造壮。當(dāng)然,每個內(nèi)容列表頁的布局和顯示內(nèi)容都是相同的骂束,不過不同的Cell自適應(yīng)解決方案所對應(yīng)的UI流暢度也是不同的耳璧。下面我們先大體的聊一下每種Cell自適應(yīng)的實現(xiàn)方案。

Autolayout + AutomaticDimension:該解決方案對應(yīng)著展箱,下方第一個Cell, 點擊該Cell進(jìn)入的頁面完全由AutoLayout進(jìn)行布局旨枯,Cell自適應(yīng)的高度也不用我們自己計算,而是使用系統(tǒng)提供的解決方案UITableViewAutomaticDimension來解決析藕。當(dāng)然召廷,使用UITableViewAutomaticDimension要依賴于你添加的約束凳厢,稍后會介紹到。這種實現(xiàn)方案用起來簡單竞慢,不過UI流暢度方面不太理想先紫。當(dāng)TableView快速滑動時,就會出現(xiàn)掉幀筹煮,卡的不要不要的遮精。

Autolayout + CountHeight:這種解決方案依然是采用AutoLayout的方式來對Cell的內(nèi)容進(jìn)行布局,不過Cell的高度我們是自己計算的败潦,當(dāng)然我們這個計算Cell高度的過程是放在子線程中進(jìn)行的本冲,所以這種實現(xiàn)方式要優(yōu)于第一種實現(xiàn)方式,稍后會詳細(xì)介紹劫扒。

FrameLayout + CountHeight:為了進(jìn)一步提高流暢度檬洞,我們采用了純Frame布局,之前好像在哪兒看過沟饥,說Autolayout最終也是會被轉(zhuǎn)換成Frame進(jìn)行布局的添怔,所以我們索性就使用Frame對整個Cell中的元素進(jìn)行布局。當(dāng)然Cell高度已經(jīng)Cell中可變內(nèi)容的高度都是在子線程中進(jìn)行計算的贤旷,這也是優(yōu)化很重要的一步广料。這種實現(xiàn)方式還是比較流暢的,可以作為折中的方案幼驶。

YYKit + CountHeight:這種解決方案用到了YYKit中的控件艾杏,并且使用Frame布局與Cell高度的計算。這種方式要由于上面的解決方案盅藻,比較YYKit中的一些控件做了優(yōu)化购桑。

AsyncDisplayKit + CountHeight:則是使用了AsyncDisplayKit中提供的相關(guān)Note代替系統(tǒng)的原生控件,這種實現(xiàn)方式是這5種實現(xiàn)方式中最為流暢的萧求。稍后會詳細(xì)介紹其兴。

上面這五種實現(xiàn)方式將是下方介紹的具體內(nèi)容,當(dāng)然會涉及一些其他的技術(shù)實現(xiàn)細(xì)節(jié)夸政。

二、博客所涉及的自定義工具介紹

在進(jìn)入主題之前榴徐,先進(jìn)行預(yù)熱守问。先對本片博客中所涉及的一些小工具進(jìn)行介紹。當(dāng)然這些工具是自己封裝的坑资,是本篇博客中所涉Demo的基礎(chǔ)耗帕,本部分將進(jìn)行統(tǒng)一介紹,在使用時我們就一筆帶過即可袱贮。

1.工具一:FPSDisplay

上述Demo中使用到了一個小的組件是FPSDisplay, 用于實時顯示屏幕的刷新頻率的仿便。我們知道現(xiàn)在iPhone的FPS是60。也就是每秒刷新60幀,如果低于60幀的話那就是掉幀了嗽仪,如果掉幀掉的多的話就會明顯的看出卡頓荒勇。上述截圖中右下方的黑色圖標(biāo)就是我們封裝的FPSDisplay工具。當(dāng)然該工具是參考著YYKit-Demo中所實現(xiàn)的闻坚,對其進(jìn)行的簡化和封裝沽翔,將其提取成了一個單獨(dú)的組件,便于在我們的應(yīng)用中引入窿凤。

下方就是FPSDisplay引入并初始化的過程仅偎,下方是在AppDelegate中的didFinishLaunchingWithOptions中添加的。因為FPSDisplay是添加在KeyWindow上的雳殊,所以在FPSDisplay初始化時要保證你的App已經(jīng)有了KeyWindow了橘沥。進(jìn)行下方初始化后,在你的App的右下方就會出現(xiàn)一個圖標(biāo)來實時的顯示FPS夯秃。

FPSDisplay的實現(xiàn)并不麻煩座咆,主要是CADisplayLink的使用,將創(chuàng)建CADisplayLink創(chuàng)建的對象添加到MainRunLoop中寝并,就可以以此來計算FPS了箫措。下方是FPSDisplay的核心代碼。在每次進(jìn)行屏幕刷新時都會執(zhí)行下方的tink方法衬潦,我們可以來計算1秒內(nèi)刷新的次數(shù)斤蔓,也就是所謂的FPS。代碼比較簡單镀岛,在此就不做過多的贅述了弦牡,詳細(xì)的代碼在Github上已經(jīng)分享。

2.工具二:數(shù)據(jù)提供者

除了上述的FPSDislay工具外漂羊,我們還需要一個模塊驾锰,那就是為Demo提供模擬數(shù)據(jù)的模塊。因為我們沒有網(wǎng)絡(luò)模塊走越,我們就模擬網(wǎng)絡(luò)請求來生成數(shù)據(jù)椭豫,然后對數(shù)據(jù)進(jìn)行處理生成Model。當(dāng)然這個生成測試數(shù)據(jù)的過程沒有用到主線程旨指,為了不阻塞Main線程赏酥,我們需要將數(shù)據(jù)生成的部分在子線程中異步的執(zhí)行。當(dāng)然此處主要涉及多線程的東西谆构。下方代碼段就是數(shù)據(jù)提供者DataSupport的核心代碼裸扶。

下方代碼段主要用到了并行隊列的異步執(zhí)行,任務(wù)組的使用搬素,已經(jīng)任務(wù)鎖的添加呵晨。下方首先創(chuàng)建了一個并行隊列concurrentQueue和隊列的任務(wù)組group魏保,并且為了數(shù)據(jù)同步,我們使用信號量創(chuàng)建了一個任務(wù)鎖lock摸屠。在for循環(huán)中我們異步的執(zhí)行并行隊列來創(chuàng)建我們需要的數(shù)據(jù)模型Model谓罗。每循環(huán)一次創(chuàng)建一個Model,為了Model數(shù)據(jù)的獨(dú)立性餐塘,在創(chuàng)建Model時妥衣,我們要為其添加信號量同步鎖。

當(dāng)50條數(shù)據(jù)異步創(chuàng)建完畢后戒傻,我們需要將其提供給數(shù)據(jù)提供者的使用放税手,也就是在任務(wù)組中的任務(wù)都執(zhí)行完畢后,會執(zhí)行下方的notify方法需纳。

在Model創(chuàng)建時芦倒,我們會對Model中可變的文字,也就是Cell中高度變化的內(nèi)容的高度進(jìn)行計算不翩。當(dāng)然該計算是在子線程中異步執(zhí)行的兵扬。所以不會占用主線程的時間來計算Cell的高度以及Cell中可變文字的高度。我們Model中有兩個字段就是來存儲Cell的高度以及可變文本的高度的口蝠,如下所示器钟。這樣做的好處就是提高UI的流暢度。

3.工具三:UIImage對象的Memory緩存

第三個工具也是為了提高數(shù)據(jù)流暢度而生的妙蔗,就是圖片的對象緩存傲霸。我們將已經(jīng)初始化過的圖片進(jìn)行緩存,等下次再使用該圖片時直接從緩存中讀取眉反,從而節(jié)省了在主線程中創(chuàng)建對象和銷毀對象的時間昙啄,從而可以提高UI的流暢度。當(dāng)然此處我實現(xiàn)的圖片的內(nèi)存緩存比較簡單寸五,也就是在本Demo中適用梳凛。不過原理還是OK的,全面的MemoryCache請參考YYKit中的YYMemoryCache梳杏。其中用到了雙向鏈表以及CFMutableDictionaryRef來實現(xiàn)的MemoryCache韧拒,其源碼并不是很難理解,有興趣的小伙伴可以進(jìn)行閱讀呢十性。

本篇博客所實現(xiàn)的Memory緩存就比較簡單了叭莫,就使用了一個字典,字典的Key是圖片的名稱烁试,字典的Value是已經(jīng)創(chuàng)建的字典的對象。代碼比較簡單拢肆,下方是核心代碼减响。大體原理就是在獲取時靖诗,如果緩存字典中沒有相應(yīng)的對象就進(jìn)行創(chuàng)建并加入緩存,然后返回該對象支示。如果緩存中已經(jīng)有該對象掖鱼,則直接返回荚醒。核心代碼如下。

三、Autolayout + AutomaticDimension

上一部分已經(jīng)為Demo的開發(fā)做好了準(zhǔn)備罢杉,接下來就開始進(jìn)入今天真正的主題。首先我們來介紹Autolayout + AutomaticDimension的實現(xiàn)方式看峻。使用這種方式來是Cell高度的自適應(yīng)比較簡單房轿,但不高效。下方是我們所使用的Cell的布局栽渴,當(dāng)然是使用AutoLayout來實現(xiàn)的尖坤。因為下方test的內(nèi)容的長度是不定的,所以我們?yōu)閠est所對應(yīng)的TextView添加的約束為(top, left right, bottom)闲擦。這樣test的高度就可以隨著Cell的高度而改變了慢味。

約束添加完畢后,我們的工作基本上就已經(jīng)完成了墅冷,接下來需要進(jìn)行簡單的配置纯路,我們的Cell高度自適應(yīng)就OK了。下方就是我們添加完約束后要做的事情寞忿,需要給我們的tableView設(shè)置一個預(yù)估值(estimatedRowHeight), 然后在TableViewDelegate的heightForRowAtIndexPath方法中返回UITableViewAutomaticDimension該屬性即可驰唬。這樣Cell就可以根據(jù)可變的文字高度來自適應(yīng)了。當(dāng)然該方法在iOS8以上的系統(tǒng)上才可以使用罐脊。

經(jīng)過上述這兩步定嗓,我們的Cell就可以進(jìn)行自適應(yīng)了,下方是該解決方案所對應(yīng)的運(yùn)行效果萍桌∠Γ可以看出來卡頓還是比較明顯的,掉幀比較嚴(yán)重上炎,在Cell高度自適應(yīng)時最好不要采用此方法恃逻。也就是說這種方法,并不適用在我們Cell列表中來預(yù)估每個Cell的高度藕施。那這種方式是不是就沒用了呢寇损?當(dāng)然不是,填寫內(nèi)容的Cell上是可以使用這種方法進(jìn)行預(yù)估的裳食,也就是說矛市,當(dāng)根據(jù)用戶輸入的內(nèi)容來實時改變Cell的高度,是可以使用該方法的诲祸。

四浊吏、Autolayout +CountHeight

接下來我們對上述的效果進(jìn)行優(yōu)化而昨,不使用TableView的預(yù)估值了,而是直接使用我們在子線程中計算的文本高度找田。當(dāng)然依然是使用AutoLayout的方式歌憨,將上述返回高度的方法heightForRowAtIndexPath中的內(nèi)容進(jìn)行替換,直接返回當(dāng)前Model中Cell的高度墩衙,如下所示:

經(jīng)過上面這么一修改务嫡,我們就可以將之前Cell高度計算的內(nèi)容移到子線程中了,上述的卡頓問題會得到些微的解決漆改。下方是該方式的運(yùn)行效果心铃,可以看出來比上述的實現(xiàn)方式稍微好一些,不過還是有些掉幀于个,掉幀也是比較嚴(yán)重的暮顺。

五、FrameLayout + CountHeight

上述結(jié)果仍然不理想捶码,我們接著優(yōu)化羽氮。我們不使用AutoLayout布局惫恼,我們直接使用Frame來布局,這樣就減少了由AutoLayout轉(zhuǎn)換到FrameLayout的時間祈纯。本部分我們就使用純代碼的方式令宿,以Autolayout進(jìn)行布局。在給Cell配置數(shù)據(jù)的時候我們根據(jù)Model中計算的高度來修改可變文字內(nèi)容的高度腕窥,如下所示:

下方是使用這種方式最終的運(yùn)行效果簇爆,從該效果中可以看出,效果還是蠻OK的响蓉。雖然有些掉幀哨毁,但是還是非常流暢的,這種流暢度是可以接受的言秸。如果你不想使用第三方庫的話,這種方式還是一個比較好的解決方案的。

六. YYKit + CountHeight

接下來我們進(jìn)一步進(jìn)行優(yōu)化抄沮,引入第三方UI組件YYKit岖瑰。將Cell上的組件替換成YYKit所提供的組件。然后使用Frame進(jìn)行布局率挣,當(dāng)然也是在子線程中對Cell的高度進(jìn)行計算了露戒。當(dāng)然此處只是對YYKit簡單的使用,應(yīng)該還有更好的優(yōu)化方式智什,只是此處沒有給出荠锭,歡迎相互交流。

看來將進(jìn)行系統(tǒng)的基礎(chǔ)控件換成了YYKit中的控件删豺,下方是此解決方案的運(yùn)行效果愧怜。單從效果上來看,還是比較流暢的赔桌,但是為達(dá)到完全不掉幀的效果渴逻。不過整體看來還是比較流暢的。

七雪位、AsyncDisplayKit + CountHeight

接下來我們要用Facebook提供的第三方庫來進(jìn)行基礎(chǔ)組件的替換雹洗,將我們使用到的組件替換成AsyncDisplayKit相應(yīng)的Note,如下所示庇茫。這些Note是對系統(tǒng)組件的重組螃成,對組件的顯示進(jìn)行了優(yōu)化,讓其渲染更為流暢宁炫。

下方就是使用AsyncDisplayKit重構(gòu)后運(yùn)行的效果羔巢。從下方的效果上來看罩阵,幾乎不掉幀,那個流暢呢袍辞。如果你對UI流暢度要求比較高的話常摧,那么AsyncDisplayKit是一個比較好的選擇。不過會嚴(yán)重依賴AsyncDisplayKit谎懦,如果AsyncDisplayKit停止維護(hù)了溃斋,后期對AsyncDisplayKit進(jìn)行替換的話梗劫,工作量還是比較大的。因為這種布局框架不像網(wǎng)絡(luò)框架蛉威,我們可以對網(wǎng)絡(luò)框架的調(diào)用進(jìn)行提取走哺,網(wǎng)絡(luò)層統(tǒng)一對外接口,很方便切換到其他網(wǎng)絡(luò)請求庫择示。但是像AsyncDisplayKit這種框架會散布于UI層的各個角落,封裝提取不易汪诉,更不用說輕而易舉的替換了剪菱。所以像這種頁面的實現(xiàn)孝常,個人還是偏向于Framelayout + CountHeight的方式來實現(xiàn)蚓哩。

八岸梨、Demo中用到的設(shè)計模式

經(jīng)過上面這7步,我們Demo的功能以及效果已經(jīng)介紹完畢半开,不同實現(xiàn)方式優(yōu)缺點一目了然赃份。該部分也是本篇博客最后一部分,我們就來聊一下本篇博客中所使用的設(shè)計模式纠永。我們可以看出上述幾個列表的頁面是完全一樣的谒拴,只是Cell的實現(xiàn)方式不同英上。所以我們可以將TableView提取成基類,TableView中所使用的Cell類型由子類來確定惭聂。說的官方一些易遣,這就是策略模式。具體的Cell使用策略由具體的TableView來定侨歉,而父類TableView值負(fù)責(zé)根據(jù)子類提供的策略來進(jìn)行Cell的初始化。

我們就以AsyncDisplayKitTableViewController和FrameCountTableViewController這兩個類為例炮温,下方就是這兩個TableViewController的相關(guān)代碼牵舵。下方這兩個類的基類都是SuperTableViewController畸颅。大部分工作都在基類中去實現(xiàn)了,而子類中只提供了使用Cell的策略涛癌。這就是策略模式的好處送火,便于擴(kuò)充种吸,如果有類似的頁面,子類只提供Cell的類型即可坚俗。下方這兩個類中的getReuseIdentifier方法就是為父類提供策略的方法坦冠。

當(dāng)然不知上述類有父類辙浑,具體Cell的基類也得有父類,因為在TableViewController中聲明Cell時用的是Cell的父類倦踢,如下所示侠草。此處用到了面向?qū)ο蟮亩鄳B(tài)性边涕,并且也用到了面向接口原則褂微。此處SuperTableViewCell雖然是一個基類园爷,但是它也擔(dān)負(fù)著定義子類接口的責(zé)任童社。好處就不多說了吧。

關(guān)于設(shè)計模式相關(guān)的內(nèi)容呀癣,請查看之前發(fā)布的關(guān)于設(shè)計模式的系列博客設(shè)計模式系列项栏,重構(gòu)的內(nèi)容的話請查看之前發(fā)布重構(gòu)系列的博客《重構(gòu)系列》蹬竖。當(dāng)然這兩個系列的博客全是使用Swift語言實現(xiàn)的Demo,不過思想都是相同的。好了今天博客篇幅也挺長的劈榨,就先到這兒吧晦嵌。

github分享鏈接:https://github.com/lizelu/DisplayTestDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惭载,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子棒妨,更是在濱河造成了極大的恐慌含长,老刑警劉巖拘泞,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陪腌,死亡現(xiàn)場離奇詭異烟瞧,居然都是意外死亡染簇,警方通過查閱死者的電腦和手機(jī)剖笙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來过蹂,“玉大人聚至,你說我怎么就攤上這事扳躬。” “怎么了击胜?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵偶摔,是天一觀的道長促脉。 經(jīng)常有香客問我,道長宫仗,這世上最難降的妖魔是什么旁仿? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任丁逝,我火速辦了婚禮霜幼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铸题。我一直安慰自己,他們只是感情好探熔,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布诀艰。 她就那樣靜靜地躺著饮六,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绿满。 梳的紋絲不亂的頭發(fā)上窟扑,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天嚎货,我揣著相機(jī)與錄音,去河邊找鬼茎毁。 笑死忱辅,一個胖子當(dāng)著我的面吹牛墙懂,可吹牛的內(nèi)容都是我干的扮念。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼弄匕!你這毒婦竟也來了迁匠?” 一聲冷哼從身側(cè)響起驹溃,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤豌鹤,失蹤者是張志新(化名)和其女友劉穎枝缔,沒想到半個月后愿卸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俱诸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年睁搭,在試婚紗的時候發(fā)現(xiàn)自己被綠了笼平。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寓调。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡夺英,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出余黎,到底是詐尸還是另有隱情载萌,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站乖坠,受9級特大地震影響熊泵,放射性物質(zhì)發(fā)生泄漏涩赢。R本人自食惡果不足惜轩勘,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一绊寻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冰蘑,春花似錦村缸、人聲如沸梯皿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽权逗。三九已至冤议,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奔垦,已是汗流浹背尸疆。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工寿弱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留症革,地道東北人鸯旁。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像艇挨,于是被迫代替她去往敵國和親缩滨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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