今天在看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需要自己準備了,要把它顯示出來。是不是很麻煩呀?沒辦法,誰讓我們在獲得知識呢,知識總是需要點功夫的。
- (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等等,你應該可以輕鬆搞定。不僅僅是表面上,更重要的是,你理解了原理,掌握了機制,萬變都不怕,即使有新的需求,腦袋想想,或者拿著這篇文章看看,希望能給你一些啟示。