本文的重點(diǎn)并不僅是UITableView的基本使用方法腐芍,而是強(qiáng)調(diào)有關(guān)UITableView和UITableViewCell開發(fā)過程中的一些具體細(xì)節(jié)問題”Χ瘢基本信息請參閱Apple開發(fā)文檔《Table View Programming Guide for iOS》。
概述
Table可能是最擅長于展示數(shù)據(jù)的一種UI部件。因此UITableView這個類是iOS App開發(fā)中除button和Label之外最常用的控件類型叶撒。幾乎任何一個App都離不開tableView。使用UITableView很簡單耐版,核心就是要實(shí)現(xiàn)兩個protocol祠够。這是由于tableView在MVC中只是V這一環(huán),所以它需要額外的支持提供另外的MC兩個環(huán)節(jié)的功能才能讓一個table完整的工作粪牲。而這種支持實(shí)現(xiàn)的途徑就是protocol古瓤,Apple要求開發(fā)者提供實(shí)現(xiàn)** UITableViewDataSource** 和UITableViewDelegate 這兩個protocol的支持類來協(xié)同對應(yīng)的UITableView的工作。一般情況下腺阳,實(shí)現(xiàn)delegate protocol的類是UITableViewController落君,而data source protocol 可以是controller負(fù)責(zé),也可以是其他helper類完成亭引。但更多情況下绎速,我更傾向于單獨(dú)的modal wrapper類來完成。現(xiàn)在很多的建議data source盡可能不要放在controller中焙蚓,這樣有利于解決mass controller的問題纹冤。可以參考o(jì)bjc.io這篇文章:《Lighter View Controllers》
鑒于UITableView是如此的重要主届,iOS替我們定制化了兩種類型的tableView:static和dynamic赵哲。前者適用于表格內(nèi)容相對固定的場景,更多的使用在諸如Setting panel君丁,Detail panel的地方枫夺;而后者適用于表格內(nèi)容不固定,行數(shù)動態(tài)變化的場景绘闷,更多的使用在網(wǎng)絡(luò)請求返回后橡庞,將動態(tài)的數(shù)據(jù)進(jìn)行內(nèi)容展示等。雖然Apple內(nèi)置了幾種(確切的說到目前為止是4種)table的style印蔗,但是對于App開發(fā)而言扒最,大多數(shù)情況下都需要自行定制table對數(shù)據(jù)的展示方式,因此相對應(yīng)的华嘹,table cell的定制化成為App開發(fā)的必修課題吧趣。
那么下文將介紹一下Static table的使用中需要注意的一些地方和dynamic table中cell開發(fā)的方法總結(jié)。
關(guān)于Static Table的報錯
static table的設(shè)置方法很簡單,在Xcode中設(shè)置UITableView的類型為static即可强挫。
這里著重強(qiáng)調(diào)一個問題岔霸,絕大多數(shù)使用static table view的人都會遇到xcode報出的一個莫名其妙的錯誤:
而這個問題的原因是:放置static table的View Controller <u> ** 必須為iOS內(nèi)置的UITableViewController類型 ** </u>
據(jù)說這是xcode的一個bug,但是到目前為止還沒有被“修復(fù)”的跡象俯渤,總而言之呆细,造成的結(jié)果就是,如果你想在某個頁面放置一個static table view八匠,你必須單獨(dú)放在UITableViewController中絮爷,而且僅僅手動將自己的viewController的類繼承自UITableViewController或者在xib中強(qiáng)制改成UITableViewController都不行,必須是原生的UITableViewController梨树】雍唬可是很多情況下我們確實(shí)需要在自己的viewcontroller中添加一個static table view。怎么辦劝萤?解決方法是使用Container View渊涝。
- 在你自己的ViewController中拖入一個Container View
-
刪除這個Container View自動創(chuàng)建的segue和對應(yīng)的target view controller
- 拖入一個新的UITableViewController,加入一個table view床嫌,修改類型為static;
-
Ctrl-drag container view到這個UITableViewController胸私,在彈出的segue類型中選擇Embed:
當(dāng)然厌处,這種方式只適用于storyboard操作并且要求支持Container VC的iOS版本。在其他情況下岁疼,可以直接使用addSubView:
阔涉,將UITableViewController的view(當(dāng)然就是tableView)加到自己的“container”view之下。
UITableViewCell的使用方法
下文的重點(diǎn)是總結(jié)UITableView和UITableViewCell的核心方法捷绒。更多詳情可以參考Apple的官方文檔瑰排。
1. 預(yù)定義Cell
iOS自定義了4種常見的Cell格式,在UITableViewCell.h中的注釋中Apple給了一些明確的提示這些預(yù)置的style一般都適用于什么場景:
typedef enum {
UITableViewCellStyleDefault, // Simple cell with text label and optional image view (behavior of UITableViewCell in iPhoneOS 2.x)
UITableViewCellStyleValue1, // Left aligned label on left and right aligned label on right with blue text (Used in Settings)
UITableViewCellStyleValue2, // Right aligned label on left with blue text and left aligned label on right (Used in Phone/Contacts)
UITableViewCellStyleSubtitle // Left aligned label on top and left aligned label on bottom with gray text (Used in iPod).
} UITableViewCellStyle
4種類型在Xcode中對應(yīng)的選項(xiàng)為:Basic, Right Detail, Left Detail和Subtitle暖侨。在iOS8下測試椭住,對應(yīng)的示例圖如下:
-
Basic:
-
Right Detail有圖時:
-
Right Detail無圖時:
-
Left Detail:
-
Subtitle:
在這4種格式都統(tǒng)一有3個property可以供你使用:
- textLabel:一個主標(biāo)題
- detailTextLabel:一個副標(biāo)題
- imageView:一張詳情縮略圖片
實(shí)際上這些預(yù)置類型就是把這3種元素做了些取舍然后在不同的位置組合了一下。我覺得在設(shè)計App的時候字逗,在任何情況下京郑,設(shè)計師和工程師都應(yīng)當(dāng)首先考慮這些預(yù)置的類型能不能滿足需求,除非有足夠的必要葫掉,否則不要輕易的浪費(fèi)經(jīng)歷在重復(fù)構(gòu)造定制化的View上些举。個人覺得Subtile模式已經(jīng)適用于絕大多數(shù)對UI要求不高的場合。你完全可以自己修改這3個控件的一些屬性來對UI進(jìn)行微調(diào)俭厚。比如你可以嘗試將imageView的大小放大一些户魏。
2. 自定義Cell
UITableView是通過調(diào)用UITableViewDataSource
中的tableView:cellForRowAtIndexPath:
方法來獲取每一行所需要的Cell的,所以絕大多自定義Cell的處理過程都是在這一步完成,另外由于UITableViewCell本身也是一個UIView叼丑,所以你也可以在UITableViewDelegate
中的tableView:willDisplayCell:forRowAtIndexPath:
方法中對Cell的view做最后的定制化关翎。
注意: Apple明確指出,在
tableView:willDisplayCell:forRowAtIndexPath:
中應(yīng)當(dāng)只修改“state-based properties”幢码,比如selection和background color等等笤休,但是不應(yīng)該是內(nèi)容(content),也就是不要在這里進(jìn)行任何數(shù)據(jù)處理症副。
2.1 自定義Cell的創(chuàng)建
有以下幾種方法:
-
方法1:使用Code繼承預(yù)定義的cell樣式店雅,然后再手動添加自己的view
這是Apple官方文檔中的示例,它通過調(diào)用initWithStyle:reuseIdentifier:
使用UITableViewCellStyleDefault
參數(shù)來創(chuàng)建一個Cell贞铣,為了方便期間闹啦,我對代碼稍做了些改動,一個是簡化了數(shù)據(jù)加載辕坝,另一個是用NSTextAlignment替換了廢棄的UITextAlignment:
#define MAINLABEL_TAG 1
#define SECONDLABEL_TAG 2
#define PHOTO_TAG 3
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"ImageOnRightCell";
static int i = 0;
UILabel *mainLabel, *secondLabel;
UIImageView *photo;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 220.0, 15.0)];
mainLabel.tag = MAINLABEL_TAG;
mainLabel.font = [UIFont systemFontOfSize:14.0];
mainLabel.textAlignment = NSTextAlignmentRight;
mainLabel.textColor = [UIColor blackColor];
mainLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:mainLabel];
secondLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 20.0, 220.0, 25.0)];
secondLabel.tag = SECONDLABEL_TAG;
secondLabel.font = [UIFont systemFontOfSize:12.0];
secondLabel.textAlignment = NSTextAlignmentRight;
secondLabel.textColor = [UIColor darkGrayColor];
secondLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:secondLabel];
photo = [[UIImageView alloc] initWithFrame:CGRectMake(225.0, 0.0, 80.0, 45.0)];
photo.tag = PHOTO_TAG;
photo.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:photo];
} else {
mainLabel = (UILabel *)[cell.contentView viewWithTag:MAINLABEL_TAG];
secondLabel = (UILabel *)[cell.contentView viewWithTag:SECONDLABEL_TAG];
photo = (UIImageView *)[cell.contentView viewWithTag:PHOTO_TAG];
}
mainLabel.text = [NSString stringWithFormat:@"Title_%d", i];
secondLabel.text = [NSString stringWithFormat:@"Description_%d", i];
i++;
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"];
photo.image = theImage;
return cell;
}
最終效果如下:
這里有兩個重點(diǎn):
Cell的重用機(jī)制
重用機(jī)制對于UITableView而言是非常重要的概念窍奋,這能夠顯著提高滑動TableView時的性能。如果不使用重用酱畅,那么每次新的Cell出現(xiàn)在屏幕上時都需要新創(chuàng)建一個琳袄,這對快速滑動而言是非常影響用戶體驗(yàn),并且會占用系統(tǒng)大量的內(nèi)存開銷纺酸。iOS的TableView使用的重用機(jī)制在原理上很簡單窖逗,就是系統(tǒng)自動維護(hù)一個queue,這個隊列放著一定數(shù)量的準(zhǔn)備好的Cell UI object餐蔬,滑出屏幕范圍之外一定“距離”的Cell都會被回收到這個queue里碎紊,給馬上要滑入屏幕范圍內(nèi)的Cell復(fù)用。
使用重用的方法核心是兩個概念:Cell Identity和dequeue樊诺。前者是標(biāo)識一種具體的Cell的id仗考,后者是將Cell從復(fù)用隊列中取出的實(shí)際操作。首先你需要用這個id告訴系統(tǒng)你將要用于重用的Cell是誰(注冊)词爬,然后你使用這個id從重用的隊列里取出來(復(fù)用)秃嗜。具體到本例中的API就是如下兩個:initWithStyle:reuseIdentifier:
和dequeueReusableCellWithIdentifier:
。后面將看到缸夹,對于不同情況下創(chuàng)建的Cell痪寻,注冊和復(fù)用的調(diào)用也并不相同。-
必須把自定義的控件添加在cell的contentView上虽惭,而不是view上橡类,也就是:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; ... // Correct! [cell.contentView addSubview:yourCustomComponetView]; // Wrong! // [cell.view addSubview:yourCustomComponentView];
再次強(qiáng)調(diào)一點(diǎn),Cell的Content View不是自己的root view芽唇。關(guān)于Cell View的結(jié)構(gòu)可以參考這篇文章:《制作一個可以滑動操作的 Table View Cell》
這里只摘出最終結(jié)論顾画,能夠讓你對一個Cell的view有直觀的理解取劫。
對于一個如圖所示的Table而言:
它的TableViewCell的View層級結(jié)構(gòu)是:
<UITableViewCell; frame = (0 396; 320 44);> //1
| <UITableViewCellScrollView; frame = (0 0; 320 44); > //2
| | <UIButton; frame = (302 16; 8 12.5)> //3
| | | <UIImageView; frame = (0 0; 8 12.5);> //4
| | <UITableViewCellContentView; frame = (0 0; 287 44);> //5
| | | <UILabel; frame = (15 0; 270 43);> //6
這個Cell 里有六個視圖:
* UITableViewCell 這是最高層的視圖。 Frame 顯示它有 320 點(diǎn)寬和 44 點(diǎn)高——寬度和高度都和預(yù)期的一致研侣,因?yàn)樗推聊灰粯訉捚仔埃叨染褪?44 點(diǎn)。
* UITableViewCellScrollView 雖然你不能直接使用這個私有類庶诡,但它的名字很好地暗示了它的功能惦银。它的 Size 和 Cell 的一樣。
* UIButton 它在 Cell 的最右邊末誓,就是 Disclosure Indicator 按鈕扯俱。
* UIImageView 是上面 UIButton 的子視圖,裝載著 Disclosure Indicator 的圖像喇澡。
* UITableViewCellContentView 另外一個私有類迅栅,它包含 Cell 的內(nèi)容。這個類對于開發(fā)者來說就是 UITableViewCell 的 contentView 屬性晴玖。但它只作為一個 UIView 來暴露在外读存,這就意味著你只在其上調(diào)用使用公開的 UIView 方法;而不能使用任何與這個類關(guān)聯(lián)的任何私有方法呕屎。
* UILabel 顯示 “Item #” 文本让簿。
很顯然,這里cell.contentView并不是cell.view秀睛。
-
方法2:使用xib繪制Custom Cell View
這是自定義Cell最常見也是最省力的方法:
- 首先在xcode中創(chuàng)建custom xib拜英,拖拽需要的控件到xib上,并做好布局和限制琅催;
2)新建自己的custom class,定義對應(yīng)的outlet properties虫给;
3)將xib的class設(shè)置成對應(yīng)的custom class藤抡,然后將outlet和控件連接起來;
4)在適當(dāng)?shù)奈恢茫ㄒ话銥槌跏蓟牡胤剑┱{(diào)用registerNib:forCellReuseIdentifier:
注冊Cell抹估;
5)在tableView:cellForRowAtIndexPath:
中調(diào)用dequeueReusableCellWithIdentifier:forIndexPath:
復(fù)用cell
下面的這個示例演示了上述步驟缠黍。在這個示例中,首先創(chuàng)建Custom Cell和xib的界面药蜻,其中有一張大圖瓷式,下面有兩個獨(dú)立的label,然后我使用了tableView:willDisplayCell:forRowAtIndexPath:
對每一行Cell的背景顏色做出最終設(shè)置语泽,而在tableView cellForRowAtIndexPath:
中對Cell的內(nèi)容進(jìn)行填充:
Custom View:
@interface MyTableCellView : UITableViewCell
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UILabel *name;
@property (weak, nonatomic) IBOutlet UILabel *date;
@end
Custom Xib:
在table View Controller中:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup here ...
...
// register the cell to tell the system preparing to reuse it.
[self.tableView registerNib:[UINib nibWithNibName:@"TableCellView" bundle:nil] forCellReuseIdentifier:cellId];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// always reuse the cell
MyTableCellView *cell = [tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath];
cell.frame = CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, 250);
NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"];
cell.imageView.image = [UIImage imageWithContentsOfFile:path];
NSDate *object = self.objects[indexPath.row];
cell.date.text = [object description];
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"indexpath = %ld", indexPath.row);
if ( indexPath.row % 2 == 0) {
cell.backgroundColor = [UIColor redColor];
}
else {
cell.backgroundColor = [UIColor greenColor];
}
}
Demo:
-
方法3:使用code定義一個Custom Cell Class
這個在本質(zhì)上和上一種使用xib的方法是一樣的贸典,只不過一個是用代碼完全定制,一個是借助xib操作踱卵。但是在Cell重用的方式上有一定區(qū)別廊驼。上一種方法中据过,需要調(diào)用registerNib:forCellReuseIdentifier:
來注冊重用的Cell,在這里就需要用registerClass:forCellReuseIdentifier:
方法來注冊妒挎。與之對應(yīng)的绳锅,獲取重用的Cell的時候捕发,調(diào)用dequeueReusableCellWithIdentifier:forIndexPath:
方法伐债。
Class myClass = [MyTableCellView class];
[self.tableView registerClass: myClass forCellReuseIdentifier:@"CustomCell"];
...
...
MyTableCellView *cell = [self.tableView dequeueReusableCellWithIdentifier:@"CustomCell" forIndexPath:path];
cell.label.text = @"text";
...
注意
dequeueReusableCellWithIdentifier:
和dequeueReusableCellWithIdentifier:forIndexPath:
的區(qū)別!!
- 如果你注冊過Cell稀拐,在沒有可用的cell時怎囚,前者會返回nil窃植;而后者永遠(yuǎn)都會從注冊的nib或者class中替你創(chuàng)建一個可用的Cell酝润。也就是說志电,前者調(diào)用你需要手動檢查nil骇笔,而后者不需要彻消;
- 如果你從沒有注冊過cell竿拆,在沒有可用的cell時,前者會返回nil宾尚,后者……直接崩潰丙笋!也就是說,調(diào)用后者你 必須確保注冊過cell 煌贴。
2.2 訪問Cell中的控件:
- xib中使用outlet
這個應(yīng)該不用多說御板,在Cell的原型中(不管是Static cell還是Dynamic cell)定義outlet properties,然后在xib中拖拽連接對應(yīng)的控件即可牛郑;Apple官方文檔上的示意圖:
- 代碼中使用viewWithTag:
這個是獲取parent view上某個特定view的快捷方法怠肋,首先需要設(shè)置一個sub view的tag,然后使用viewWithTag:來訪問這個sub view淹朋。設(shè)置時可以通過xcode在xib中設(shè)置tag標(biāo)簽笙各,也可以直接通過tag property手動設(shè)置:
創(chuàng)建時:
mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 220.0, 15.0)];
mainLabel.tag = MAINLABEL_TAG;
// customize the label here...
[cell.contentView addSubview:mainLabel];
訪問時:
mainLabel = (UILabel *)[cell.contentView viewWithTag:MAINLABEL_TAG];
有關(guān)Table和Cell的性能需要注意的問題
關(guān)于這個話題,Apple并沒有用過多的篇幅介紹础芍,但是在官方開發(fā)文檔中明確提出了3點(diǎn)意見:
注意重用(Reuse Cell)
這點(diǎn)我們已經(jīng)在上文中著重強(qiáng)調(diào)過了杈抢。避免反復(fù)的調(diào)整Cell的布局(Avoid relayout of content)
只在創(chuàng)建每一個Cell的時候布局一次,而不要每一次獲取Cell的時候都去重新布局仑性。避免透明的subviews (Use opaque subviews)
自定義cell的時候惶楼,盡可能避免使用透明的控件,因?yàn)橥该骺丶趖able滑動時將增大渲染開銷诊杆。
總結(jié)
本文主要從Static table和Dynamic table兩方面總結(jié)了UITableView和UITableViewCell的核心使用方法和問題歼捐,并著重介紹了Custom Cell的幾種方法和注意事項(xiàng)。
另外晨汹,有興趣的話豹储,UITableView還有另外幾個比較關(guān)鍵的功能可以繼續(xù)研究,一個是Editing mode宰缤,一個是Table Index颂翼,還有一個相對也很重要的功能自定義Header和Footer View晃洒。有空的話可以再總結(jié)一下。希望此篇能幫助到您朦乏。
2016年4月5日球及,完稿于南京。