UITableVIew是iOS開發(fā)中最常見的視圖中最經(jīng)典的視圖了,沒有之一智亮,相信對(duì)這個(gè)視圖敢稱精通的人開發(fā)個(gè)好應(yīng)用應(yīng)該是問題不大的退疫。
閑話少敘,進(jìn)入正題鸽素。
怎么使用
掌握兩個(gè)代理
-
UITableViewDelegate
@optional //下文再提到該方法用heightForRow代替 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
-
UITableViewDataSource
@required
//下文再提到該方法用numberOfRowsInSection代替
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
//下文再提到該方法用cellForRow代替
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;@optional //下文再提到該方法用numberOfSection代替 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
要想比較完整的展示你的數(shù)據(jù)褒繁,這四個(gè)方法是最經(jīng)常被實(shí)現(xiàn)的。
調(diào)用過程大體是這樣的:tableView
會(huì)先詢問代理(在一般MVC里大部分是當(dāng)前視圖控制器ViewController)要展示多少個(gè)section
,就是調(diào)用numberOfSections
,如果代理沒有實(shí)現(xiàn)該方法默認(rèn)就是1個(gè)section
馍忽。然后tableView
調(diào)用numberOfRowsInSection
先詢問第一個(gè)section
有多少個(gè)cell
,然后挨個(gè)執(zhí)行heightForRow
獲取每個(gè)cell的高度棒坏。tableView
對(duì)每個(gè)section
都執(zhí)行一遍這樣的操作后,那么結(jié)果來了:tableView
通過對(duì)這些cell
高度的累加就知道了需要多大的空間才能安放得了所有的內(nèi)容遭笋,于是它調(diào)整好了contentSize
的值坝冕。這樣走下來就為我們后續(xù)在滑動(dòng)時(shí)能通過scrollIndicator
觀察到我們大體滑到了哪個(gè)位置做好了準(zhǔn)備。
準(zhǔn)備好空間之后接下來的任務(wù)就是準(zhǔn)備內(nèi)容了瓦呼。當(dāng)然大家都知道真正的內(nèi)容是依附在UITableViewCell
上的喂窟,tableView
先調(diào)用cellForRow
去獲取代理返回給它的第一個(gè)cell
,對(duì)于所有的cell
來說width
都是固定的央串,即tableView
本身的寬度磨澡,對(duì)于第一個(gè)cell
來說它的origin
也是確定的,即(0,0)
质和,也就是說要想確定這個(gè)cell
的位置就只需要知道它的height
了稳摄。于是tableView
再去調(diào)用heightForRow
去獲取它的高度,這樣一個(gè)視圖能確定顯示在屏幕什么位置的充要條件就具備了饲宿。剩下的cell
同理厦酬,挨個(gè)放在上一個(gè)cell
的下邊就行了。
總結(jié)一下:
- 調(diào)用
numberOfSection
獲得 A個(gè)section
- 先調(diào)用
numberOfRowsInSection
獲得B個(gè)cell
,再調(diào)用heightForRow
B次瘫想。如此循環(huán)A次 - 循環(huán)調(diào)用
cellForRow
和heightForRow
仗阅,直到cell
的個(gè)數(shù)充滿當(dāng)前屏幕。
這就是一個(gè)普通的tableView
一開始加載數(shù)據(jù)的過程国夜,有幾點(diǎn)需要說明:
- 如果你展示在每個(gè)
cell
上的內(nèi)容是相對(duì)固定的减噪,準(zhǔn)確點(diǎn)說是每個(gè)cell
的高度是固定的,那么heightForRow
是不建議讓代理去實(shí)現(xiàn)的,而是通過tableView
的rowHeight
屬性來代替旋廷,當(dāng)數(shù)據(jù)量比較大鸠按,比如說有10000個(gè)(其實(shí)只要 >= 2)cell
時(shí)礼搁,tableView
只需要10000*rowHeight
就知道應(yīng)該準(zhǔn)備的空間大小了饶碘,而不是調(diào)用一個(gè)方法10000次通過累加獲知需要的大小。而且你懂的馒吴,要想獲取一個(gè)cell
的高度并不是那么容易的事扎运,尤其是在自動(dòng)布局出現(xiàn)之前,你需要計(jì)算各種字符串的所占空間的大小饮戳,這對(duì)性能是相當(dāng)大的損耗豪治。 - 如果每個(gè)
cell
高度確實(shí)不一樣,數(shù)據(jù)量又很大時(shí)該怎么解決這個(gè)性能問題呢扯罐,iOS7之后系統(tǒng)提供了估算高度的辦法负拟,estimatedRowHeight
和- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath//下文再提到該方法用estimateHeightForRow代替
,這樣每次在加載數(shù)據(jù)之前,tableView
不再通過heightForRow
消耗大量的性能獲取空間大小了歹河,而是通過在estimateRowHeight
或者estimatedHeightForRow
不需要費(fèi)勁計(jì)算就能獲取的一個(gè)估算值來獲取一個(gè)大體的空間大小掩浙,等到真正的加載數(shù)據(jù)時(shí)才根據(jù)獲取真實(shí)數(shù)據(jù),并做出相應(yīng)的調(diào)整秸歧,比如contentSize
或者scrollIndicator
的位置厨姚。關(guān)于動(dòng)態(tài)計(jì)算高度,推薦羊教授的一篇文章優(yōu)化UITableViewCell高度計(jì)算的那些事 - 這些方法的調(diào)用在保證大順序不變的情況下键菱,每個(gè)方法的調(diào)用次數(shù)是不一定的谬墙,每個(gè)iOS版本又不一樣,你如果想知道可以動(dòng)手去試驗(yàn)一下经备。尤其是在iOS8,它認(rèn)為
cell
會(huì)隨時(shí)變化拭抬,所以一滑動(dòng)就重新計(jì)算cell
的高度。 - 這些方法的調(diào)用其實(shí)也是有插曲的侵蒙,比如調(diào)用了
reloadData
之后玖喘,tableView
只會(huì)調(diào)用能讓它知道所需空間大小的代理方法,然后立馬執(zhí)行reloadData
之后的語句蘑志,也就說cellForRow
并不會(huì)在reloadData
之后緊接著執(zhí)行累奈。所以reloadData
之后盡量避免對(duì)數(shù)據(jù)源數(shù)組的操作。
復(fù)用機(jī)制
了解UITableView
的人肯定對(duì)這一著名特性多少有點(diǎn)了解急但。咱們先假設(shè)UITableView
沒有復(fù)用機(jī)制澎媒,那么我們要展示10000條數(shù)據(jù)的話,那就得生成10000個(gè)UITableViewCell
,占用了大量?jī)?nèi)存不說波桩,性能也可想而知了戒努,必然是一滑一卡頓,一頓一暴怒啊,控制力弱的估計(jì)要摔手機(jī)了储玫。
復(fù)用機(jī)制大體是這樣:UITableView
首先加載一屏幕(假設(shè)UITableView
的大小是整個(gè)屏幕的大小)所需要的UITableViewCell
,具體個(gè)數(shù)要根據(jù)每個(gè)cell
的高度而定侍筛,總之肯定要鋪滿整個(gè)屏幕,更準(zhǔn)確說當(dāng)前加載的cell
的高度要大于屏幕高度。然后你往上滑動(dòng)撒穷,想要查看更多的內(nèi)容匣椰,那么肯定需要一個(gè)新的cell
放在已經(jīng)存在內(nèi)容的下邊。這時(shí)候先不去生成端礼,而是先去UITableView
自己的一個(gè)資源池里去獲取禽笑。這個(gè)資源池里放了已經(jīng)生成的而且能用的cell
。如果資源池是空的話才會(huì)主動(dòng)生成一個(gè)新的cell
蛤奥。那么這個(gè)資源池里的cell
又來自哪里呢佳镜?當(dāng)你滑動(dòng)時(shí)視圖是,位于最頂部的cell
會(huì)相應(yīng)的往上滑動(dòng)凡桥,直到它徹底消失在屏幕上蟀伸,消失的cell
去了哪里呢?你肯定想到了缅刽,是的啊掏,它被UITableView
放到資源池里了。其他cell
也是這樣拷恨,只要一滑出屏幕就放入資源池脖律。這樣,有進(jìn)有出腕侄,總共需要大約一屏幕多一點(diǎn)的cell
就夠了小泉。相對(duì)于1000來說節(jié)省的資源就是指數(shù)級(jí)啊,完美解決了性能問題冕杠。
iOS6之后我們一般在代碼里這樣處理cell
先注冊(cè)
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];
或
[self.tableView registerNib:[UINib nibWithNibName:@"NibTableViewCell" bundle:nil] forCellReuseIdentifier:@"NibTableViewCell"];-
在代理方法里獲取
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath]; // do something return cell; }
那么具體在代碼里是怎么實(shí)現(xiàn)的呢微姊?我們可以大膽的猜測(cè)一下。
UITableView
有幾個(gè)屬性(假想的):
NSMutableDictionary *registerCellInfo;
NSMutableDictionary *reusableCellsDictionary;
NSMutableArray *visibleCells;
我們推測(cè)兩個(gè)注冊(cè)方法的實(shí)現(xiàn)
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier{
[self.registerCellInfo setObject:nib forKey:identifier];
[self.registerCellsDictionary setObject:[NSMutableArray array] forKey:identifier];
}
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier{
[self.registerCellInfo setObject:cellClass forKey:identifier];
[self.registerCellsDictionary setObject:[NSMutableArray array] forKey:identifier];
}
然后推測(cè)最關(guān)鍵的獲取方法
- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath{
//indexPath這個(gè)參數(shù)是為了重置`cell`的大小分预,相關(guān)的處理并不是本文的重點(diǎn)兢交,所以暫不實(shí)現(xiàn)
NSMutableArray *array = self.reusableCellsDictionary[identifier];
UITableViewCell *cell = nil;
if(array.count){
cell = array.lastObject;
[self.visibleCells addObject:cell];
[array removeLastObject];
}else{
id obj = self.registerCellInfo[identifier];
if([obj isKindOfClass:[UINib class]]){
cell = [[((UINib *)obj) instantiateWithOwner:nil options:nil] lastObject];
}else{
cell = [[(Class)obj alloc] init];
}
if(cell){
[self.visibleCells addObject:cell];
}
}
return cell;
}
??,請(qǐng)忽略以上所有推測(cè)方法的不嚴(yán)謹(jǐn)笼痹,許多該有的條件判斷并沒有去處理配喳。但是寫到這里相信親愛的讀者已經(jīng)了解了UITableView
復(fù)用機(jī)制的原理了。現(xiàn)在凳干,你已經(jīng)具備了自己動(dòng)手寫一個(gè)UITableView
的基礎(chǔ)了(當(dāng)然晴裹,假設(shè)你已經(jīng)對(duì)UIScrollView
有了充足的了解)。如果我的文章對(duì)你有用救赐,煩請(qǐng)點(diǎn)個(gè)喜歡涧团,好激勵(lì)我繼續(xù)寫下去。。泌绣。
關(guān)于UITableView
的更多知識(shí)我們后續(xù)再談