今天在看iphone開發秘籍的時候,遇到這個問題,就仔細的深入了一下,通過測試,獲取了一些自認為還不錯的結論,希望對大家在cell復用方面遇到的一些問題會有所幫助。
本篇文章只講原理,對於如果對cell做界面,不深入講述。鑒於我的表達能力有限,可能會有我自己清楚,但是卻說不清楚的地方,如有問題,留言給我。
UITableView在界面的編程用的甚多,iphone開發也三月有余了,每次用到cellForRowAtIndexPath的委托方法的時候,都是直接copy代碼,自己略加一些界面的修改,對於cell的標示符都是static NSString* identifier = @"cell";然後調用dequeueReusableCellWithIdentifier方法獲取cell,如果cell為空,再調用[[[UITableViewCellalloc]initWithStyle方法新創建一個,根本沒有考慮過更深一些的東西。為了講解清楚,現放上一段代碼,代碼copy自iphone開發秘籍,本人為了講解,略加修改。以下所有講解均依照此代碼進行,因此,如果您希望能夠透徹的了解cell的復用機制,建議實際運行以下,跟著講解,查看效果。代碼只有tableVIew的委托方法,因此您需要自己創建工程,把這些委托方法加進去。
上文一共四個委托方法,表明tableView一個section,有32行,高度為58.關於tableView的高度那個委托方法的返回的高度值,建議最好自己運行程序查看以下,最終達到一頁的界面顯示8個cell,第8個cell顯示一半也行,但是不能顯示9和7個cell。我這裡之所以為58,由於(480 - 44)/ 58 為7.5 的樣子,44為navigationBar的高度,480為屏幕的高度。總之,需要達到的效果是一頁顯示7個多的cell。還有那個icon.png需要自己准備了,要把它顯示出來。是不是很麻煩呀?沒辦法,誰讓我們在獲得知識呢,知識總是需要點功夫的。
[cpp]
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
return 32;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 58;
}
- (UITableViewCell *)tableView:(UITableView *)tView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCellStyle style;
NSString *cellType;
switch (indexPath.row % 4)
{
case 0:
style = UITableViewCellStyleDefault;
cellType = @"Default Style";
//有標題沒有正文(沒有細節文字)。可選的圖片
break;
case 1:
style = UITableViewCellStyleSubtitle;
cellType = @"Subtitle Style";
//標題和正文方式,上下排布。可選的圖片
break;
case 2:
style = UITableViewCellStyleValue1;
cellType = @"Value1 Style";
//左邊文字左對齊,右邊文字右對齊。可選的圖片
break;
case 3:
style = UITableViewCellStyleValue2;
cellType = @"Value2 Style";
//左邊文字右對齊,藍色;右邊文字左對齊,黑色。沒有圖片
break;
}
static int i = 0;
UITableViewCell *cell = [tView dequeueReusableCellWithIdentifier:cellType];
if (!cell)
{
cell = [[[UITableViewCell alloc] initWithStyle:style reuseIdentifier:cellType] autorelease];
++i;
NSLog(@"cell created %d times", i);
}
if (indexPath.row > 3)
cell.imageView.image = [UIImage imageNamed:@"icon.png"];
cell.textLabel.text = cellType;
cell.detailTextLabel.text = @"Subtitle text";
return cell;
}
首先講解一下復用隊列:
復用隊列的元素增加:只有在cell被滑動出界面的時候,此cell才會被加入到復用隊列中。每次在創建cell的時候,程序會首先通過調用dequeueReusableCellWithIdentifier:cellType方法,到復用隊列中去尋找標示符為“cellType”的cell,如果找不到,返回nil,然後程序去通過調用[[[UITableViewCell alloc] initWithStyle:style reuseIdentifier:cellType] autorelease]來創建標示符為“cellType”的cell。
先運行一次程序,不要滑動cell,查看打印日志,會有打印"cell create 1 times",,,"cell create 8 times",一共有8個日志,表明cell創建了8次,這個日志打印是在cellForRowAtIndexPath中的創建cell的時候打印的。然後慢慢向下(向下的意思,實際上是手向上滑動,讓界面顯示下一個cell,向上與之相反)滑動cell,到顯示第九個cell的時候,會看到打印一條日志"cell create 9 times",然後繼續慢慢向下滑動,會有"cell create 10 times",直到滑動到第12個cell以後,cell被創建了12個之後,以後再怎麼滑動,cell都不會被創建了。也就是說本tableView要完整的工作,一共創建了12個cell。
開始解釋原因了:
第一頁的界面一共需要展示8個cell,故而cell需要創建8次,每一個cell負責自己的數據顯示。此8個創建以後,復用隊列依然為空(因為你此時還沒有滑動cell呢,復用隊列的元素不會增加)。然後在向下滑動顯示出第9個cell的時候,還會調用cellForRowAtIndexPath方法,在此方法中,它首先到可復用隊列中去找,由於此時隊列為空,它創建了一個cell,打印日志,同時當第1個cell滑動出界面之外,第一個cell進入到復用隊列中,隊列中有一個元素,此元素的表示符為@"Default Style"。然後繼續向下滑動cell,開始顯示第10個cell,它同樣到復用隊列中去找,第10個cell的標示符為@"Subtitle Style",但是隊列中唯一的cell的標示符為@"Default Style",根據標示符尋找,沒有找到,故而再次創建一個新的cell,同時將滑動出界面的第2個cell進入復用隊列。此時復用隊列有兩個元素,標示符為@"Default Style",@"Subtitle Style"。同樣的道理,滑動到第11個cell的時候,第3個cell入隊,第12個cell的時候,第4個cell入隊。此時復用隊列的元素個數為四個,標示符分別為:@"Default Style",@"Subtitle Style",@"Value1 Style",@"Value2 Style".然後繼續滑動cell,當滑動到第13個cell的時候,它的標示符為@"Default Style",它到復用隊列中去找,可以找到。故而,這個cell就不會被創建了,同理,再次向下滑動,以下的所有cell都可以根據標示符找到對應的cell,不會有被創建的。在向下滑動的過程中,滑動出去的cell會被入隊,不過只入隊創建了的cell,也就是說最終隊列中會有12個cell。對於每次取對應標示符的元素,到底取的是哪一個?采用的什麼策略?這個我不知道,我通過打印查看的是在向下滑動的過程中,一直順著隊列找,隊列是一個循環隊列。向上滑動的時候,逆著隊列向上找,由於是循環隊列,一直在轉圈。
如果你仔細看的話,你會發現第一個cell本來沒有圖片,為什麼一劃下去再次滑動(如果滑動的距離遠,一次搞定,如果滑動的距離近,多上下滑動幾次)上來又有圖片了呢?這個就要思考一個:第一個cell在滑出界面又劃入界面的時候,是從復用隊列拿到的。復用隊列有12個元素,第1,5,9還第一個cell有相同的標示符,第1個沒有圖片,第5個和第9個有圖片。當用戶復用的是第一個cell的時候,它是沒有圖片,當用戶復用第5,9個cell的時候,它是有圖片的。因此,當你上下滑動,查看第一個cell的時候,你會看到它一會有圖片,一會沒有圖片。這個跟復用時候的隊列查找規則有關。
實用篇:
說了那麼多,全是關於原理的。現在說點實用的。如果你想在所有的cell中添加一個按鈕,你是應該在if中添加,還是應該在if之外添加呢?毫無疑問,應該在if中,如果你是在if的外面添加的,那會導致,你在向下滑動cell的過程中,取出來的cell本來已經帶有button了,而你還在addSubview,按鈕越來越多。或者你可以采用在if外面添加,前提是每次先cell remove掉其所有的子視圖。這樣太消耗cpu,麻煩了。如果你想一行隔著一行有按鈕和沒有按鈕,你該怎麼做呢?稍微思考一下,這個可是兩種風格的cell,故而在滑出界面進行重用的時候,它們應該屬於不同的標示符。於是你在創建cell的時候,應該去指定兩種標示符,創建兩種cell。當然,也許你聰明了,我是不是可以在if之外先remove掉cell的所有子視圖,然後根據row % 2 == 0或者!=0 來進行addSubView:button嗎?答案當然是肯定的,但是這樣還是同樣的問題,太消耗cpu了。平常我們給每一個cell添加了一個button,肯定要添加事件的。對於不同的button,響應不同的事件?那麼我是不是通過在if語句中給button設置它的tag標記(例如button.tag = indexpath.row)來實現呢?哈哈,你應該足夠聰明了吧,當然不行。你可以這樣想,if語句是用來創建的,它只被執行了(例如上面的例子:12次),但是你可能有幾百行的cell,當然你的tag也就只有12個了,明顯不對應。像這樣的,應該怎麼處理呢?答案是:放在if的外面。你在if外面設置了tag標記,當然,在某一個具體的時間點上,仍然只有12個標記,但是這12個標記是可變的,例如當前界面顯示第100-111號的cell,那麼此時的button的tag就會是100-111了,仍然是12個按鈕,但是它們會根據用戶的滑動,進行不同的tag切換,相當於擁有了很多個按鈕。如果你沒有被我說的話給弄暈,腦袋又足夠清醒的話,你應該可以得出以下的結論:對於界面的定制,放在if中比較好,一個cell中只創建一次;對於數據的定制,放在if外面比較好,對於不同的cell,表示不同的內容,雖然只有12個cell,但是cell中存放的數據我可以任意的映射。如果你得出了這個結論,那麼如果在加上textField,label等等,你應該可以輕松搞定。不僅僅是表面上,更重要的是,你理解了原理,掌握了機制,萬變都不怕,即使有新的需求,腦袋想想,或者拿著這篇文章看看,希望能給你一些啟示。
作者:lipeng08