看TableView的資料其實已經蠻久了,一直想寫點兒東西,卻老是由於各類緣由拖延,今天晚上有時間靜下心來記錄一些最近學習的TableView的知識。下面進入正題,UITableView堪稱UIKit裏面最複雜的一個控件了,使用起來不算難,可是要用好並不容易。當使用的時候咱們必需要考慮到後臺數據的設計,tableViewCell的設計和重用以及tableView的效率等問題。 數組
下面分9個方面進行介紹: app
1、UITableView概述 框架
UITableView繼承自UIScrollView,能夠表現爲Plain和Grouped兩種風格,分別以下圖所示: 函數
其中左邊的是Plain風格的,右邊的是Grouped風格,這個區別仍是很明顯的。 佈局
查看UITableView的幫助文檔咱們會注意到UITableView有兩個Delegate分別爲:dataSource和delegate。 性能
dataSource是UITableViewDataSource類型,主要爲UITableView提供顯示用的數據(UITableViewCell),指定UITableViewCell支持的編輯操做類型(insert,delete和reordering),並根據用戶的操做進行相應的數據更新操做,若是數據沒有更具操做進行正確的更新,可能會致使顯示異常,甚至crush。 學習
delegate是UITableViewDelegate類型,主要提供一些可選的方法,用來控制tableView的選擇、指定section的頭和尾的顯示以及協助完成cell的刪除和排序等功能。 ui
提到UITableView,就必須的說一說NSIndexPath。UITableView聲明瞭一個NSIndexPath的類別,主要用來標識當前cell的在tableView中的位置,該類別有section和row兩個屬性,前者標識當前cell處於第幾個section中,後者表明在該section中的第幾行。 this
UITableView只能有一列數據(cell),且只支持縱向滑動,當建立好的tablView第一次顯示的時候,咱們須要調用其reloadData方法,強制刷新一次,從而使tableView的數據更新到最新狀態。 spa
2、UITableViewController簡介
UITableViewController是系統提供的一個便利類,主要是爲了方便咱們使用UITableView,該類生成的時候就將自身設置成了其包含的tableView的dataSource和delegate,並建立了不少代理函數的框架,爲咱們大大的節省了時間,咱們能夠經過其tableView屬性獲取該controller內部維護的tableView對象。默認狀況下使用UITableViewController建立的tableView是充滿全屏的,若是須要用到tableView是不充滿全屏的話,咱們應該使用UIViewController本身建立和維護tableView。
UITableViewController提供一個初始化函數initWithStyle:,根據須要咱們能夠建立Plain或者Grouped類型的tableView,當咱們使用其從UIViewController繼承來的init初始化函數的時候,默認將會咱們建立一個Plain類型的tableView。
UITableViewController默認的會在viewWillAppear的時候,清空全部選中cell,咱們能夠經過設置self.clearsSelectionOnViewWillAppear = NO,來禁用該功能,並在viewDidAppear中調用UIScrollView的flashScrollIndicators方法讓滾動條閃動一次,從而提示用戶該控件是能夠滑動的。
3、UITableViewCell介紹
UITableView中顯示的每個單元都是一個UITableViewCell對象,看文檔的話咱們會發現其初始化函數initWithStyle:reuseIdentifier:比較特別,跟咱們平時看到的UIView的初始化函數不一樣。這個主要是爲了效率考慮,由於在tableView快速滑動的滑動的過程當中,頻繁的alloc對象是比較費時的,因而引入了cell的重用機制,這個也是咱們在dataSource中要重點注意的地方,用好重用機制會讓咱們的tableView滑動起來更加流暢。
咱們能夠經過cell的selectionStyle屬性指定cell選中時的顯示風格,以及經過accessoryType來指定cell右邊的顯示的內容,或者直接指定accessoryView來定製右邊顯示的view。
系統提供的UITableView也包含了四種風格的佈局,分別是:
typedef enum { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle } UITableViewCellStyle;
這幾種文檔中都有詳細描述,這兒就不在累贅。然而能夠想象系統提供的只是最經常使用的幾種類型,當系統提供的風格不符合咱們須要的時候,咱們就須要對cell進行定製了,有如下兩種定製方式可選:
1、直接向cell的contentView上面添加subView
這是比較簡單的一種的,根據佈局須要咱們能夠在不一樣的位置添加subView。可是此處須要注意:全部添加的subView都最好設置爲不透明的,由於若是subView是半透明的話,view圖層的疊加將會花費必定的時間,這會嚴重影響到效率。同時若是每一個cell上面添加的subView個數過多的話(一般超過3,4個),效率也會受到比較大的影響。
下面咱們看一個例子:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sectionModal = [sections objectAtIndex:indexPath.section]; static NSString *reuseIdetify = @"SvTableViewCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdetify]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdetify]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.showsReorderControl = YES; for (int i = 0; i < 6; ++i) { UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100 + 15 * i, 0, 30, 20)]; label.backgroundColor = [UIColor redColor]; label.text = [NSString stringWithFormat:@"%d", i]; [cell.contentView addSubview:label]; [label release]; } } cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.text = [sectionModal.cityNames objectAtIndex:indexPath.row]; return cell; }
在上面這個例子中,我往每一個cell中添加了6個subView,並且每一個subView都是半透明(UIView默認是半透明的),這個時候滑動起來明顯就能夠感受到有點顫抖,不是很流暢。當把每個subView的opaque屬性設置成YES的時候,滑動會比以前流暢一些,不過仍是有點兒卡。
2、從UITableViewCell派生一個類
經過從UITableViewCell中派生一個類,能夠更深度的定製一個cell,能夠指定cell在進入edit模式的時候如何相應等等。最簡單的實現方式就是將全部要繪製的內容放到一個定製的subView中,而且重載該subView的drawRect方法直接把要顯示的內容繪製出來(這樣能夠避免subView過多致使的性能瓶頸),最後再將該subView添加到cell派生類中的contentView中便可。可是這樣定製的cell須要注意在數據改變的時候,經過手動調用該subView的setNeedDisplay方法來刷新界面,這個例子能夠在蘋果的幫助文檔中的TableViewSuite工程中找到,這兒就不舉例了。
觀看這兩種定製cell的方法,咱們會發現subView都是添加在cell的contentView上面的,而不是直接加到cell上面,這樣寫也是有緣由的。下面咱們看一下cell在正常狀態下和編輯狀態下的構成圖:
cell在正常狀態下的構成圖以下:
進入編輯狀態下cell的構成圖以下:
經過觀察上面兩幅圖片咱們能夠看出來,當cell在進入編輯狀態的時候,contentView會自動的縮放來給Editing control騰出位置。這也就是說若是咱們把subView添加到contentView上,若是設置autoresizingMask爲更具父view自動縮放的話,cell默認的機制會幫咱們處理進入編輯狀態的狀況。並且在tableView是Grouped樣式的時候,會爲cell設置一個背景色,若是咱們直接添加在cell上面的話,就須要本身考慮到這個背景色的顯示問題,若是添加到contentView上,則能夠經過view的疊加幫助咱們完成該任務。綜上,subView最好仍是添加到cell的contentView中。
4、Reordering
爲了使UITableVeiew進入edit模式之後,若是該cell支持reordering的話,reordering控件就會臨時的把accessaryView覆蓋掉。爲了顯示reordering控件,咱們必須將cell的showsReorderControl屬性設置成YES,同時實現dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法。咱們還能夠同時經過實現dataSource中的tableView:canMoveRowAtIndexPath:返回NO,來禁用某一些cell的reordering功能。
下面看蘋果官方的一個reordering流程圖:
上圖中當tableView進入到edit模式的時候,tableView會去對當前可見的cell逐個調用dataSource的tableView:canMoveRowAtIndexPath:方法(此處官方給出的流程圖有點兒問題),決定當前cell是否顯示reoedering控件,當開始進入拖動cell進行拖動的時候,每滑動過一個cell的時候,會去掉用delegate的tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:方法,去判斷當前劃過的cell位置是否能夠被替換,若是不行則給出建議的位置。當用戶放手時本次reordering操做結束,調用dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法更新tableView對應的數據。
此處給個我寫demo中的更新數據的小例子:
// if you want show reordering control, you must implement moveRowAtndexPath, or the reordering control will not show // when use reordering end, this method is invoke - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { // update DataModal NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sourceSectionModal = [sections objectAtIndex:sourceIndexPath.section]; NSString *city = [[sourceSectionModal.cityNames objectAtIndex:sourceIndexPath.row] retain]; [sourceSectionModal.cityNames removeObject:city]; [SvTableViewDataModal replaceSectionAtIndex:sourceIndexPath.section withSection:sourceSectionModal]; SvSectionModal *desinationsSectionModal= [[SvTableViewDataModal sections] objectAtIndex:destinationIndexPath.section]; [desinationsSectionModal.cityNames insertObject:city atIndex:destinationIndexPath.row]; [SvTableViewDataModal replaceSectionAtIndex:destinationIndexPath.section withSection:desinationsSectionModal]; [city release]; }
上面代碼中首先拿到源cell所處的section,而後從該section對應的數據中移除,而後拿到目標section的數據,而後將源cell的數據添加到目標section中,並更新回數據模型,若是咱們沒有正確更新數據模型的話,顯示的內容將會出現異常。
5、Delete & Insert
cell的delete和insert操做大部分流程都是同樣的,當進入編輯模式的時候具體的顯示是delete仍是insert取決與該cell的editingStyle的值,editStyle的定義以下:
typedef enum { UITableViewCellEditingStyleNone, UITableViewCellEditingStyleDelete, UITableViewCellEditingStyleInsert } UITableViewCellEditingStyle;
當tableView進入編輯模式之後,cell上面顯示的delete仍是insert除了跟cell的editStyle有關,還與 tableView的delegate的tableView:editingStyleForRowAtIndexPath:方法的返回值有關(在這裏嘮叨一句,其實delegate提供了不少改變cell屬性的機會,如非必要,仍是不要去實現這些方法,由於執行這些方法也形成必定的開銷)。
delete和insert的流程以下蘋果官方文檔中給出的圖所示:
下面是我寫的demo中刪除和添加部分的代碼:
#pragma mark - - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"commit editStyle: %d", editingStyle); if (editingStyle == UITableViewCellEditingStyleDelete) { NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sourceSectionModal = [sections objectAtIndex:indexPath.section]; [sourceSectionModal.cityNames removeObjectAtIndex:indexPath.row]; [SvTableViewDataModal replaceSectionAtIndex:indexPath.section withSection:sourceSectionModal]; [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight]; } else { // do something for add it NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sourceSectionModal = [sections objectAtIndex:indexPath.section]; [sourceSectionModal.cityNames insertObject:@"new City" atIndex:indexPath.row]; [SvTableViewDataModal replaceSectionAtIndex:indexPath.section withSection:sourceSectionModal]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight]; } }
代碼中首先判斷當前操做是delete操做仍是insert操做,相應的更新數據,最後根據狀況調用tableView的insertRowsAtIndexPaths:withRowAnimation:或者deleteRowsAtIndexPaths:withRowAnimation:方法,對tableView的視圖進行更新。cell的刪除和添加操做相對仍是比較簡單的。
6、Cell的Select操做
當咱們在tableView中點擊一個cell的時候,將會調用tableView的delegate中的tableView:didSelectRowAtIndexPath:方法。
關於tableView的cell的選中,蘋果官方有如下幾個建議:
1、不要使用selection來代表cell的選擇狀態,而應該使用accessaryView中的checkMark或者自定義accessaryView來顯示選中狀態。
2、當選中一個cell的時候,你應該取消前一個cell的選中。
3、若是cell選中的時候,進入下一級viewCOntroller,你應該在該級菜單從navigationStack上彈出的時候,取消該cell的選中。
這塊兒再提一點,當一個cell的accessaryType爲UITableViewCellAccessoryDisclosureIndicator的時候,點擊該accessary區域一般會將消息繼續向下傳遞,即跟點擊cell的其餘區域同樣,將會掉delegate的tableView:didSelectRowAtIndexPath:方法,當時若是accessaryView爲 UITableViewCellAccessoryDetailDisclosureButton的時候,點擊accessaryView將會調用delegate的 tableView:accessoryButtonTappedForRowWithIndexPath:方法。
7、批量插入,刪除,部分更新操做
UITableView提供了一個批量操做的特性,這個功能在一次進行多個row或者scetion的刪除,插入,獲取更新多個cell內容的時候特別好用。全部的批量操做須要包含在beginUpdates和endUpdates塊中,不然會出現異常。
下面請看我demo中的一個批量操做的例子:
- (void)groupEdit:(UIBarButtonItem*)sender { [_tableView beginUpdates]; // first update the data modal [_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationTop]; [_tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop]; [SvTableViewDataModal deleteSectionAtIndex:0]; SvSectionModal *section = [[SvTableViewDataModal sections] objectAtIndex:0]; [section.cityNames insertObject:@"帝都" atIndex:0]; [SvTableViewDataModal replaceSectionAtIndex:0 withSection:section]; [_tableView endUpdates]; }
上面的例子中咱們能夠看到先往tableView的第0個section的第0行添加一個cell,而後將第0個section刪掉。按照咱們程序中寫的順序,那麼新添加進去的「帝都」,將不在會顯示,由於包含它的整個section都已經被刪除了。
執行程序先後結果以下圖:
demo中第0個section是陝西省的城市,第1個section是北京。左邊是執行前的截圖,右邊是執行後的截圖,觀察發現結果並不像咱們前面推測的那樣。那是由於在批量操做時,無論代碼中先寫的添加操做仍是刪除操做,添加操做都會被推出執行,直到這個塊中全部的刪除操做都執行完之後,纔會執行添加操做,這也就是上面蘋果官方圖片上要表達的意思。
蘋果官方文檔有一副圖能夠幫助咱們更好的理解這一點:
原圖中操做是:首先刪除section 0中的row 1,而後刪除section 1,再向section 1中添加一行。執行完批量更新之後就獲得右半邊的結果。
8、IndexList
當咱們tableView中section有不少,數據量比較大的時候咱們能夠引入indexList,來方便完成section的定位,例如系統的通信錄程序。咱們能夠經過設置tableView的sectionIndexMinimumDisplayRowCount屬性來指定當tableView中多少行的時候開始顯示IndexList,默認的設置是NSIntegerMax,即默認是不顯示indexList的。
爲了可以使用indexlist咱們還須要實現dataSource中一下兩個方法:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView; - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;
第一個方法返回用於顯示在indexList中的內容的數組,一般爲A,B,C...Z。第二個方法的主要做用是根據用戶在indexList中點擊的位置,返回相應的section的index值。這個例子能夠在蘋果官方給出的TableViewSuite中找到,實現起來仍是很簡單的。
9、其餘
1、分割線
咱們能夠經過設置tableView的separatorStyle屬性來設置有無分割線以及分割線的風格,其中style定義以下:
typedef enum { UITableViewCellSeparatorStyleNone, UITableViewCellSeparatorStyleSingleLine, UITableViewCellSeparatorStyleSingleLineEtched } UITableViewCellSeparatorStyle;
同時還能夠經過tableView的separatorColor屬性來設置分割線的顏色。
2、如何提升tableView的性能
a、重用cell
咱們都知道申請內存是須要時間,特別是在一段時間內頻繁的申請內存將會形成很大的開銷,並且上tebleView中cell大部分狀況下佈局都是同樣的,這個時候咱們能夠經過回收重用機制來提升性能。
b、避免content的從新佈局
儘可能避免在重用cell時候,對cell的從新佈局,通常狀況在在建立cell的時候就將cell佈局好。
c、使用不透明的subView
在定製cell的時候,將要添加的subView設置成不透明的會大大減小多個view層疊加時渲染所須要的時間。
d、若是方便,直接重載subView的drawRect方法
若是定製cell的過程當中須要多個小的元素的話,最好直接對要顯示的多個項目進行繪製,而不是採用添加多個subView。
e、tableView的delegate的方法如非必要,儘可能不要實現
tableView的delegate中的不少函數提供了對cell屬性的進一步控制,好比每一個cell的高度,cell是否能夠編輯,支持的edit風格等,如非必要最好不要實現這些方法由於快速的調用這些方法也會影響性能。