UITableView概述

一、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的刪除和排序等功能。

  提到UITableView,就必須的說一說NSIndexPath。UITableView聲明瞭一個NSIndexPath的類別,主要用來標識當前cell的在tableView中的位置,該類別有section和row兩個屬性,前者標識當前cell處於第幾個section中,後者代表在該section中的第幾行。

  UITableView只能有一列數據(cell),且只支持縱向滑動,當創建好的tablView第一次顯示的時候,我們需要調用其reloadData方法,強制刷新一次,從而使tableView的數據更新到最新狀態。

 

二、UITableViewController簡介

  UITableViewController是系統提供的一個便利類,主要是爲了方便我們使用UITableView,該類生成的時候就將自身設置成了其包含的tableViewdataSourcedelegate,並創建了很多代理函數的框架,爲我們大大的節省了時間,我們可以通過其tableView屬性獲取該controller內部維護的tableView對象。默認情況下使用UITableViewController創建的tableView是充滿全屏的,如果需要用到tableView是不充滿全屏的話,我們應該使用UIViewController自己創建和維護tableView

  UITableViewController提供一個初始化函數initWithStyle:,根據需要我們可以創建Plain或者Grouped類型的tableView,當我們使用其從UIViewController繼承來的init初始化函數的時候,默認將會我們創建一個Plain類型的tableView 

  UITableViewController默認的會在viewWillAppear的時候,清空所有選中cell,我們可以通過設置self.clearsSelectionOnViewWillAppear = NO,來禁用該功能,並在viewDidAppear中調用UIScrollViewflashScrollIndicators方法讓滾動條閃動一次,從而提示用戶該控件是可以滑動的。 

 

三、UITableViewCell介紹

   UITableView中顯示的每一個單元都是一個UITableViewCell對象,看文檔的話我們會發現其初始化函數initWithStyle:reuseIdentifier:比較特別,跟我們平時看到的UIView的初始化函數不同。這個主要是爲了效率考慮,因爲在tableView快速滑動的滑動的過程中,頻繁的alloc對象是比較費時的,於是引入了cell的重用機制,這個也是我們在dataSource中要重點注意的地方,用好重用機制會讓我們的tableView滑動起來更加流暢。

  我們可以通過cellselectionStyle屬性指定cell選中時的顯示風格,以及通過accessoryType來指定cell右邊的顯示的內容,或者直接指定accessoryView來定製右邊顯示的view 

  系統提供的UITableView也包含了四種風格的佈局,分別是:


typedef enum {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
} UITableViewCellStyle;

  這幾種文檔中都有詳細描述,這兒就不在累贅。然而可以想象系統提供的只是最常用的幾種類型,當系統提供的風格不符合我們需要的時候,我們就需要對cell進行定製了,有以下兩種定製方式可選:

  1、直接向cellcontentView上面添加subView

  這是比較簡單的一種的,根據佈局需要我們可以在不同的位置添加subView。但是此處需要注意:所有添加的subView都最好設置爲不透明的,因爲如果subView是半透明的話,view圖層的疊加將會花費一定的時間,這會嚴重影響到效率。同時如果每個cell上面添加的subView個數過多的話(通常超過34),效率也會受到比較大的影響。

  下面我們看一個例子:

複製代碼

- (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中添加了6subView,而且每個subView都是半透明(UIView默認是半透明的),這個時候滑動起來明顯就可以感覺到有點顫抖,不是很流暢。當把每一個subViewopaque屬性設置成YES的時候,滑動會比之前流暢一些,不過還是有點兒卡。

  2、從UITableViewCell派生一個類

  通過從UITableViewCell中派生一個類,可以更深度的定製一個cell,可以指定cell在進入edit模式的時候如何相應等等。最簡單的實現方式就是將所有要繪製的內容放到一個定製的subView中,並且重載該subViewdrawRect方法直接把要顯示的內容繪製出來(這樣可以避免subView過多導致的性能瓶頸),最後再將該subView添加到cell派生類中的contentView中即可。但是這樣定製的cell需要注意在數據改變的時候,通過手動調用該subViewsetNeedDisplay方法來刷新界面,這個例子可以在蘋果的幫助文檔中的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中。

 

四、Reordering

  爲了使UITableVeiew進入edit模式以後,如果該cell支持reordering的話,reordering控件就會臨時的把accessaryView覆蓋掉。爲了顯示reordering控件,我們必須將cellshowsReorderControl屬性設置成YES,同時實現dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法。我們還可以同時通過實現dataSource中的 tableView:canMoveRowAtIndexPath:返回NO,來禁用某一些cellreordering功能。

  下面看蘋果官方的一個reordering流程圖:

  上圖中當tableView進入到edit模式的時候,tableView會去對當前可見的cell逐個調用dataSourcetableView:canMoveRowAtIndexPath:方法(此處官方給出的流程圖有點兒問題),決定當前cell是否顯示reoedering控件,當開始進入拖動cell進行拖動的時候,每滑動過一個cell的時候,會去掉用delegatetableView: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中,並更新回數據模型,如果我們沒有正確更新數據模型的話,顯示的內容將會出現異常。

 

五、Delete & Insert

  celldeleteinsert操作大部分流程都是一樣的,當進入編輯模式的時候具體的顯示是delete還是insert取決與該celleditingStyle的值,editStyle的定義如下:


typedef enum {
    UITableViewCellEditingStyleNone,
    UITableViewCellEditingStyleDelete,
    UITableViewCellEditingStyleInsert
} UITableViewCellEditingStyle;

  當tableView進入編輯模式以後,cell上面顯示的delete還是insert除了跟celleditStyle有關,還與 tableViewdelegatetableView:editingStyleForRowAtIndexPath:方法的返回值有關(在這裏嘮叨一句,其實delegate提供了很多改變cell屬性的機會,如非必要,還是不要去實現這些方法,因爲執行這些方法也造成一定的開銷)

  deleteinsert的流程如下蘋果官方文檔中給出的圖所示:

  下面是我寫的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的刪除和添加操作相對還是比較簡單的。

 

六、CellSelect操作

  當我們在tableView中點擊一個cell的時候,將會調用tableViewdelegate中的tableView:didSelectRowAtIndexPath:方法。

  關於tableView的cell的選中,蘋果官方有以下幾個建議:

   1、不要使用selection來表明cell的選擇狀態,而應該使用accessaryView中的checkMark或者自定義accessaryView來顯示選中狀態。 

   2、當選中一個cell的時候,你應該取消前一個cell的選中。 

   3、如果cell選中的時候,進入下一級viewCOntroller,你應該在該級菜單從navigationStack上彈出的時候,取消該cell的選中。

  這塊兒再提一點,當一個cellaccessaryTypeUITableViewCellAccessoryDisclosureIndicator的時候,點擊該accessary區域通常會將消息繼續向下傳遞,即跟點擊cell的其他區域一樣,將會掉delegatetableView:didSelectRowAtIndexPath:方法,當時如果accessaryView爲 UITableViewCellAccessoryDetailDisclosureButton的時候,點擊accessaryView將會調用delegate的 tableView:accessoryButtonTappedForRowWithIndexPath:方法。

  

七、批量插入,刪除,部分更新操作

  UITableView提供了一個批量操作的特性,這個功能在一次進行多個row或者scetion的刪除,插入,獲取更新多個cell內容的時候特別好用。所有的批量操作需要包含在beginUpdatesendUpdates塊中,否則會出現異常。

  下面請看我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中添加一行。執行完批量更新以後就得到右半邊的結果。

   

八、IndexList

  當我們tableViewsection有很多,數據量比較大的時候我們可以引入indexList,來方便完成section的定位,例如系統的通訊錄程序。我們可以通過設置tableViewsectionIndexMinimumDisplayRowCount屬性來指定當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中找到,實現起來還是很簡單的。

 

九、其他

  1、分割線

  我們可以通過設置tableViewseparatorStyle屬性來設置有無分割線以及分割線的風格,其中style定義如下:


typedef enum {
    UITableViewCellSeparatorStyleNone,
    UITableViewCellSeparatorStyleSingleLine,
    UITableViewCellSeparatorStyleSingleLineEtched
} UITableViewCellSeparatorStyle;

  同時還可以通過tableViewseparatorColor屬性來設置分割線的顏色。

  2、如何提高tableView的性能

  a、重用cell

  我們都知道申請內存是需要時間,特別是在一段時間內頻繁的申請內存將會造成很大的開銷,而且上tebleView中cell大部分情況下佈局都是一樣的,這個時候我們可以通過回收重用機制來提高性能。

  b、避免content的重新佈局

  儘量避免在重用cell時候,對cell的重新佈局,一般情況在在創建cell的時候就將cell佈局好。

  c、使用不透明的subView

  在定製cell的時候,將要添加的subView設置成不透明的會大大減少多個view層疊加時渲染所需要的時間。

  d、如果方便,直接重載subView的drawRect方法

  如果定製cell的過程中需要多個小的元素的話,最好直接對要顯示的多個項目進行繪製,而不是採用添加多個subView。

  etableViewdelegate的方法如非必要,儘量不要實現

  tableView的delegate中的很多函數提供了對cell屬性的進一步控制,比如每個cell的高度,cell是否可以編輯,支持的edit風格等,如非必要最好不要實現這些方法因爲快速的調用這些方法也會影響性能。

發佈了26 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章