iOS開發系列–UITableView全面解析 – iPhone手機開發 iPhone軟體開發教學課程

具體代碼:http://pan.baidu.com/s/1jG5RpFS

在iOS開發中UITableView可以說是使用最廣泛的控件,我們平時使用的軟件中到處都可以看到它的影子,類似於微信、QQ、新浪微博等軟件基本上隨處都是UITableView。當然它的廣泛使用自然離不開它強大的功能,今天這篇文章將針對UITableView重點展開討論。今天的主要內容包括:

基本介紹  數據源代理  性能優化  UITableViewCell常用操作  UITableViewControllerMVC模式

基本介紹

UITableView有兩種風格:UITableViewStylePlain和UITableViewStyleGrouped。這兩者操作起來其實並沒有本質區別,隻是後者按分組樣式顯示前者按照普通樣式顯示而已。大傢先看一下兩者的應用:

1>分組樣式

UITableViewStyleGrouped1UITableViewStyleGrouped2

2>不分組樣式

UITableViewStylePlain1UITableViewStylePlain2

大傢可以看到在UITableView中數據隻有行的概念,並沒有列的概念,因為在手機操作系統中顯示多列是不利於操作的。UITableView中每行數據都是一個UITableViewCell,在這個控件中為瞭顯示更多的信息,iOS已經在其內部設置好瞭多個子控件以供開發者使用。如果我們查看UITableViewCell的聲明文件可以發現在內部有一個UIView控件(contentView,作為其他元素的父控件)、兩個UILable控件(textLabel、detailTextLabel)、一個UIImage控件(imageView),分別用於容器、顯示內容、詳情和圖片。使用效果類似於微信、QQ信息列表:

UITableViewCell1UITableViewCell2

當然,這些子控件並不一定要全部使用,具體操作時可以通過UITableViewCellStyle進行設置,具體每個枚舉表示的意思已經在代碼中進行瞭註釋:

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,    // 左側顯示textLabel(不顯示detailTextLabel),imageView可選(顯示在最左邊)
    UITableViewCellStyleValue1,        // 左側顯示textLabel、右側顯示detailTextLabel(默認藍色),imageView可選(顯示在最左邊)
    UITableViewCellStyleValue2,        // 左側依次顯示textLabel(默認藍色)和detailTextLabel,imageView可選(顯示在最左邊)
    UITableViewCellStyleSubtitle    // 左上方顯示textLabel,左下方顯示detailTextLabel(默認灰色),imageView可選(顯示在最左邊)
};

數據源

由於iOS是遵循MVC模式設計的,很多操作都是通過代理和外界溝通的,但對於數據源控件除瞭代理還有一個數據源屬性,通過它和外界進行數據交互。 對於UITableView設置完dataSource後需要實現UITableViewDataSource協議,在這個協議中定義瞭多種 數據操作方法,下面通過創建一個簡單的聯系人管理進行演示:

首先我們需要創建一個聯系人模型KCContact

KCContact.h

//
//  Contact.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import 

@interface KCContact : NSObject

#pragma mark 姓
@property (nonatomic,copy) NSString *firstName;
#pragma mark 名
@property (nonatomic,copy) NSString *lastName;
#pragma mark 手機號碼
@property (nonatomic,copy) NSString *phoneNumber;

#pragma mark 帶參數的構造函數
-(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber;

#pragma mark 取得姓名
-(NSString *)getName;


#pragma mark 帶參數的靜態對象初始化方法
+(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber;
@end

KCContact.m

//
//  Contact.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContact.h"

@implementation KCContact

-(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
    if(self=[super init]){
        self.firstName=firstName;
        self.lastName=lastName;
        self.phoneNumber=phoneNumber;
    }
    return self;
}

-(NSString *)getName{
    return [NSString stringWithFormat:@"%@ %@",_lastName,_firstName];
}

+(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
    KCContact *contact1=[[KCContact alloc]initWithFirstName:firstName andLastName:lastName andPhoneNumber:phoneNumber];
    return contact1;
}

@end

為瞭演示分組顯示我們不妨將一組數據也抽象成模型KCContactGroup

KCContactGroup.h

//
//  KCContactGroup.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import 
#import "KCContact.h"

@interface KCContactGroup : NSObject

#pragma mark 組名
@property (nonatomic,copy) NSString *name;

#pragma mark 分組描述
@property (nonatomic,copy) NSString *detail;

#pragma mark 聯系人
@property (nonatomic,strong) NSMutableArray *contacts;

#pragma mark 帶參數個構造函數
-(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts;

#pragma mark 靜態初始化方法
+(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts;

@end

KCContactGroup.m

//
//  KCContactGroup.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactGroup.h"

@implementation KCContactGroup


-(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts{
    if (self=[super init]) {
        self.name=name;
        self.detail=detail;
        self.contacts=contacts;
    }
    return self;
}

+(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts{
    KCContactGroup *group1=[[KCContactGroup alloc]initWithName:name andDetail:detail andContacts:contacts];
    return group1;
}
@end

然後在viewDidLoad方法中創建一些模擬數據同時實現數據源協議方法:

KCMainViewController.m

//
//  KCMainViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController (){
    UITableView *_tableView;
    NSMutableArray *_contacts;//聯系人模型
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //創建一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,註意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    

    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];

    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];

}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    NSLog(@"計算分組數");
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    NSLog(@"計算每組(組%i)行數",section);
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個結構體,記錄瞭組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    UITableViewCell *cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    return cell;
}

#pragma mark 返回每組頭標題名稱
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    NSLog(@"生成組(組%i)名稱",section);
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 返回每組尾部說明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    NSLog(@"生成尾部(組%i)詳情",section);
    KCContactGroup *group=_contacts[section];
    return group.detail;
}
@end

運行可以看到如下效果:

Contact

大傢在使用iPhone通訊錄時會發現右側可以按字母檢索,使用起來很方便,其實這個功能使用UITableView實現很簡單,隻要實現數據源協議的一個方法,構建一個分組標題的數組即可實現。數組元素的內容和組標題內容未必完全一致,UITableView是按照數組元素的索引和每組數據索引順序來定位的而不是按內容查找。

#pragma mark 返回每組標題索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSLog(@"生成組索引");
    NSMutableArray *indexs=[[NSMutableArray alloc]init];
    for(KCContactGroup *group in _contacts){
        [indexs addObject:group.name];
    }
    return indexs;
}

效果如下:

UITableViewIndex

需要註意的是上面幾個重點方法的執行順序,請看下圖:

image

值得指出的是生成單元格的方法並不是一次全部調用,而是隻會生產當前顯示在界面上的單元格,當用戶滾動操作時再顯示其他單元格。

註意:隨著我們的應用越來越復雜,可能經常需要調試程序,在iOS中默認情況下不能定位到錯誤代碼行,我們可以通過如下設置讓程序定位到出錯代碼行:Show the Breakpoint navigator—Add Exception breakpoint。

 

代理

上面我們已經看到通訊錄的簡單實現,但是我們發現單元格高度、分組標題高度以及尾部說明的高度都需要調整,此時就需要使用代理方法。UITableView代理方法有很多,例如監聽單元格顯示周期、監聽單元格選擇編輯操作、設置是否高亮顯示單元格、設置行高等。

1.設置行高

#pragma mark - 代理方法
#pragma mark 設置分組標題內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    if(section==0){
        return 50;
    }
    return 40;
}

#pragma mark 設置每行高度(每行高度可以不一樣)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 45;
}

#pragma mark 設置尾部說明內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    return 40;
}

2.監聽點擊

在iOS中點擊某聯系個人就可以呼叫這個聯系人,這時就需要監聽點擊操作,這裡就不演示呼叫聯系人操作瞭,我們演示一下修改人員信息的操作。

KCMainViewContrller.m

//
//  KCMainViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController (){
    UITableView *_tableView;
    NSMutableArray *_contacts;//聯系人模型
    NSIndexPath *_selectedIndexPath;//當前選中的組和行
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //創建一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,註意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    

    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];

    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];

}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    NSLog(@"計算分組數");
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    NSLog(@"計算每組(組%i)行數",section);
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄瞭組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    UITableViewCell *cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    return cell;
}

#pragma mark 返回每組頭標題名稱
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    NSLog(@"生成組(組%i)名稱",section);
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 返回每組尾部說明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    NSLog(@"生成尾部(組%i)詳情",section);
    KCContactGroup *group=_contacts[section];
    return group.detail;
}

#pragma mark 返回每組標題索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSLog(@"生成組索引");
    NSMutableArray *indexs=[[NSMutableArray alloc]init];
    for(KCContactGroup *group in _contacts){
        [indexs addObject:group.name];
    }
    return indexs;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    if(section==0){
        return 50;
    }
    return 40;
}

#pragma mark 設置每行高度(每行高度可以不一樣)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 45;
}

#pragma mark 設置尾部說明內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    return 40;
}

#pragma mark 點擊行
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    _selectedIndexPath=indexPath;
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    //創建彈出窗口
    UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"System Info" message:[contact getName] delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
    alert.alertViewStyle=UIAlertViewStylePlainTextInput; //設置窗口內容樣式
    UITextField *textField= [alert textFieldAtIndex:0]; //取得文本框
    textField.text=contact.phoneNumber; //設置文本框內容
    [alert show]; //顯示窗口
}

#pragma mark 窗口的代理方法,用戶保存數據
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    //當點擊瞭第二個按鈕(OK)
    if (buttonIndex==1) {
        UITextField *textField= [alertView textFieldAtIndex:0];
        //修改模型數據
        KCContactGroup *group=_contacts[_selectedIndexPath.section];
        KCContact *contact=group.contacts[_selectedIndexPath.row];
        contact.phoneNumber=textField.text;
        //刷新表格
        [_tableView reloadData];
    }
}

#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}
@end

在上面的代碼中我們通過修改模型來改變UI顯示,這種方式是經典的MVC應用,在後面的代碼中會經常看到。當然UI的刷新使用瞭UITableView的reloadData方法,該方法會重新調用數據源方法,包括計算分組、計算每個分組的行數,生成單元格等刷新整個UITableView。當然這種方式在實際開發中是不可取的,我們不可能因為修改瞭一個人的信息就刷新整個UITableViewView,此時我們需要采用局部刷新。局部刷新使用起來很簡單,隻需要調用UITableView的另外一個方法:

#pragma mark 窗口的代理方法,用戶保存數據
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    //當點擊瞭第二個按鈕(OK)
    if (buttonIndex==1) {
        UITextField *textField= [alertView textFieldAtIndex:0];
        //修改模型數據
        KCContactGroup *group=_contacts[_selectedIndexPath.section];
        KCContact *contact=group.contacts[_selectedIndexPath.row];
        contact.phoneNumber=textField.text;
        //刷新表格
        NSArray *indexPaths=@[_selectedIndexPath];//需要局部刷新的單元格的組、行
        [_tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];//後面的參數代表更新時的動畫
    }
}

性能優化

前面已經說過UITableView中的單元格cell是在顯示到用戶可視區域後創建的,那麼如果用戶往下滾動就會繼續創建顯示在屏幕上的單元格,如果用戶向上滾動返回到查看過的內容時同樣會重新創建之前已經創建過的單元格。如此一來即使UITableView的內容不是太多,如果用戶反復的上下滾動,內存也會瞬間飆升,更何況很多時候UITableView的內容是很多的(例如微博展示列表,基本向下滾動是沒有底限的)。

前面一節中我們曾經提到過如何優化UIScrollView,當時就是利用有限的UIImageView動態切換其內容來盡可能減少資源占用。同樣的,在UITableView中也可以采用類似的方式,隻是這時我們不是在滾動到指定位置後更改滾動的位置而是要將當前沒有顯示的Cell重新顯示在將要顯示的Cell的位置然後更新其內容。原因就是UITableView中的Cell結構佈局可能是不同的,通過重新定位是不可取的,而是需要重用已經不再界面顯示的已創建過的Cell。

當然,聽起來這麼做比較復雜,其實實現起來很簡單,因為UITableView已經為我們實現瞭這種機制。在UITableView內部有一個緩存池,初始化時使用initWithStyle:(UITableViewCellStyle) reuseIdentifier:(NSString *)方法指定一個可重用標識,就可以將這個cell放到緩存池。然後在使用時使用指定的標識去緩存池中取得對應的cell然後修改cell內容即可。

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄瞭組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    
    //由於此方法調用十分頻繁,cell的標示聲明成靜態變量有利於性能優化
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //如果緩存池沒有到則重新創建並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    NSLog(@"cell:%@",cell);
    return cell;
}

上面的代碼中已經打印瞭cell的地址,如果大傢運行測試上下滾動UITableView會發現滾動時創建的cell地址是初始化時已經創建的。

這裡再次給大傢強調兩點:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)方法調用很頻繁,無論是初始化、上下滾動、刷新都會調用此方法,所有在這裡執行的操作一定要註意性能;可重用標識可以有多個,如果在UITableView中有多類結構不同的Cell,可以通過這個標識進行緩存和重新;

UITableViewCell

1.自帶的UITableViewCell

UITableViewCell是構建一個UITableView的基礎,在UITableViewCell內部有一個UIView控件作為其他內容的容器,它上面有一個UIImageView和兩個UILabel,通過UITableViewCellStyle屬性可以對其樣式進行控制。其結構如下:

UITableViewCellStuct

有時候我們會發現很多UITableViewCell右側可以顯示不同的圖標,在iOS中稱之為訪問器,點擊可以觸發不同的事件,例如設置功能:

UITableViewCellAccesZ喎?https://www.2cto.com/kf/ware/vc/vcnlUeXBl” border=”0″ height=”480″ src=”https://www.2cto.com/uploadfile/Collfiles/20160507/20160507090910260.png” title=”UITableViewCellAccesoryType” width=”270″ />

要設置這些圖標隻需要設置UITableViewCell的accesoryType屬性,這是一個枚舉類型具體含義如下:

typedef NS_ENUM(NSInteger, UITableViewCellAccessoryType) {
    UITableViewCellAccessoryNone,                   // 不顯示任何圖標
    UITableViewCellAccessoryDisclosureIndicator,    // 跳轉指示圖標
    UITableViewCellAccessoryDetailDisclosureButton, // 內容詳情圖標和跳轉指示圖標
    UITableViewCellAccessoryCheckmark,              // 勾選圖標
    UITableViewCellAccessoryDetailButton NS_ENUM_AVAILABLE_IOS(7_0) // 內容詳情圖標
};

例如在最近通話中我們通常設置為詳情圖標,點擊可以查看聯系人詳情:

UITableViewCellAccessoryTypeDetaiButtonl

很明顯iOS設置中第一個accessoryType不在枚舉之列,右側的訪問器類型是UISwitch控件,那麼如何顯示自定義的訪問器呢?其實隻要設置UITableViewCell的accessoryView即可,它支持任何UIView控件。假設我們在通訊錄每組第一行放一個UISwitch,同時切換時可以輸出對應信息:

//
//  KCMainViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController (){
    UITableView *_tableView;
    NSMutableArray *_contacts;//聯系人模型
    NSIndexPath *_selectedIndexPath;//當前選中的組和行
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //創建一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,註意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    

    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];

    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];

}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    NSLog(@"計算分組數");
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    NSLog(@"計算每組(組%i)行數",section);
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄瞭組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    
    //由於此方法調用十分頻繁,cell的標示聲明成靜態變量有利於性能優化
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    static NSString *cellIdentifierForFirstRow=@"UITableViewCellIdentifierKeyWithSwitch";
    //首先根據標示去緩存池取
    UITableViewCell *cell;
    if (indexPath.row==0) {
        cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifierForFirstRow];
    }else{
        cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    }
    //如果緩存池沒有取到則重新創建並放到緩存池中
    if(!cell){
        if (indexPath.row==0) {
            cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierForFirstRow];
            UISwitch *sw=[[UISwitch alloc]init];
            [sw addTarget:self action:@selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
            cell.accessoryView=sw;

        }else{
            cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
            cell.accessoryType=UITableViewCellAccessoryDetailButton;
        }
    }
    
    if(indexPath.row==0){
        ((UISwitch *)cell.accessoryView).tag=indexPath.section;
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    NSLog(@"cell:%@",cell);
    
    return cell;
}

#pragma mark 返回每組頭標題名稱
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    NSLog(@"生成組(組%i)名稱",section);
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 返回每組尾部說明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    NSLog(@"生成尾部(組%i)詳情",section);
    KCContactGroup *group=_contacts[section];
    return group.detail;
}

#pragma mark 返回每組標題索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSLog(@"生成組索引");
    NSMutableArray *indexs=[[NSMutableArray alloc]init];
    for(KCContactGroup *group in _contacts){
        [indexs addObject:group.name];
    }
    return indexs;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    if(section==0){
        return 50;
    }
    return 40;
}

#pragma mark 設置每行高度(每行高度可以不一樣)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 45;
}

#pragma mark 設置尾部說明內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    return 40;
}

#pragma mark 點擊行
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    _selectedIndexPath=indexPath;
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    //創建彈出窗口
    UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"System Info" message:[contact getName] delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
    alert.alertViewStyle=UIAlertViewStylePlainTextInput; //設置窗口內容樣式
    UITextField *textField= [alert textFieldAtIndex:0]; //取得文本框
    textField.text=contact.phoneNumber; //設置文本框內容
    [alert show]; //顯示窗口
}

#pragma mark 窗口的代理方法,用戶保存數據
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    //當點擊瞭第二個按鈕(OK)
    if (buttonIndex==1) {
        UITextField *textField= [alertView textFieldAtIndex:0];
        //修改模型數據
        KCContactGroup *group=_contacts[_selectedIndexPath.section];
        KCContact *contact=group.contacts[_selectedIndexPath.row];
        contact.phoneNumber=textField.text;
        //刷新表格
        NSArray *indexPaths=@[_selectedIndexPath];//需要局部刷新的單元格的組、行
        [_tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];//後面的參數代碼更新時的動畫
    }
}


#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}


#pragma mark 切換開關轉化事件
-(void)switchValueChange:(UISwitch *)sw{
    NSLog(@"section:%i,switch:%i",sw.tag, sw.on);
}
@end

最終運行效果:

Run

註意:

由於此時我們需要兩種UITableViewCell樣式,考慮到性能我們需要在緩存池緩存兩種Cell。 UISwitch繼承於UIControl而不是UIView(當然UIControl最終也是繼承於UIView),繼承於UIControl的控件使用addTarget添加對應事件而不是代理,同時有“是否可用”、“是否高亮”、“是否選中”等屬性;上面代碼中如果有些UITableViewCell的UISwitch設置為on當其他控件重用時狀態也是on,解決這個問題可以在模型中設置對應的屬性記錄其狀態,在生成cell時設置當前狀態(為瞭盡可能簡化上面的代碼這裡就不再修復這個問題);

2.自定義UITableViewCell

雖然系統自帶的UITableViewCell已經夠強大瞭,但是很多時候這並不能滿足我們的需求。例如新浪微博的Cell就沒有那麼簡單:

UITableViewCell

沒錯,這個界面佈局也是UITableView實現的,其中的內容就是UITableViewCell,隻是這個UITableViewCell是用戶自定義實現的。當然要實現上面的UITableViewCell三言兩語我們是說不完的,這裡我們實現一個簡化版本,界面原型如下:

UITableViewCellPrototypeDesign

我們對具體控件進行拆分:

UITableViewCellPrototypeDesign2

在這個界面中有2個UIImageView控件和4個UILabel,整個界面顯示效果類似於新浪微博的消息內容界面,但是又在新浪微博基礎上進行瞭精簡以至於利用現有知識能夠順利開發出來。

在前面的內容中我們的數據都是手動構建的,在實際開發中自然不會這麼做,這裡我們不妨將微博數據存儲到plist文件中然後從plist文件讀取數據構建模型對象(實際開發微博當然需要進行網絡數據請求,這裡隻是進行模擬就不再演示網絡請求的內容)。假設plist文件內容如下:

StatusInfoPlist

接下來就定義一個KCStatusTableViewCell實現UITableViewCell,一般實現自定義UITableViewCell需要分為兩步:第一初始化控件;第二設置數據,重新設置控件frame。原因就是自定義Cell一般無法固定高度,很多時候高度需要隨著內容改變。此外由於在單元格內部是無法控制單元格高度的,因此一般會定義一個高度屬性用於在UITableView的代理事件中設置每個單元格高度。

1.首先看一下微博模型KCStatus,這個模型主要的方法就是根據plist字典內容生成微博對象:

KCStatus.h

//
//  KCStatus.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import 

@interface KCStatus : NSObject

#pragma mark - 屬性
@property (nonatomic,assign) long long Id;//微博id
@property (nonatomic,copy) NSString *profileImageUrl;//頭像
@property (nonatomic,copy) NSString *userName;//發送用戶
@property (nonatomic,copy) NSString *mbtype;//會員類型
@property (nonatomic,copy) NSString *createdAt;//創建時間
@property (nonatomic,copy) NSString *source;//設備來源
@property (nonatomic,copy) NSString *text;//微博內容



#pragma mark - 方法
#pragma mark 根據字典初始化微博對象
-(KCStatus *)initWithDictionary:(NSDictionary *)dic;

#pragma mark 初始化微博對象(靜態方法)
+(KCStatus *)statusWithDictionary:(NSDictionary *)dic;
@end

KCStatus.m

//
//  KCStatus.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatus.h"

@implementation KCStatus

#pragma mark 根據字典初始化微博對象
-(KCStatus *)initWithDictionary:(NSDictionary *)dic{
    if(self=[super init]){
        self.Id=[dic[@"Id"] longLongValue];
        self.profileImageUrl=dic[@"profileImageUrl"];
        self.userName=dic[@"userName"];
        self.mbtype=dic[@"mbtype"];
        self.createdAt=dic[@"createdAt"];
        self.source=dic[@"source"];
        self.text=dic[@"text"];
    }
    return self;
}

#pragma mark 初始化微博對象(靜態方法)
+(KCStatus *)statusWithDictionary:(NSDictionary *)dic{
    KCStatus *status=[[KCStatus alloc]initWithDictionary:dic];
    return status;
}

-(NSString *)source{
    return [NSString stringWithFormat:@"來自 %@",_source];
}
@end

2.然後看一下自定義的Cell

KCStatusTableViewCell.h

//
//  KCStatusTableViewCell.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import 
@class KCStatus;

@interface KCStatusTableViewCell : UITableViewCell

#pragma mark 微博對象
@property (nonatomic,strong) KCStatus *status;

#pragma mark 單元格高度
@property (assign,nonatomic) CGFloat height;

@end

KCStatusTableViewCell.m

//
//  KCStatusTableViewCell.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatusTableViewCell.h"
#import "KCStatus.h"
#define KCColor(r,g,b) [UIColor colorWithHue:r/255.0 saturation:g/255.0 brightness:b/255.0 alpha:1] //顏色宏定義
#define kStatusTableViewCellControlSpacing 10 //控件間距
#define kStatusTableViewCellBackgroundColor KCColor(251,251,251)
#define kStatusGrayColor KCColor(50,50,50)
#define kStatusLightGrayColor KCColor(120,120,120)

#define kStatusTableViewCellAvatarWidth 40 //頭像寬度
#define kStatusTableViewCellAvatarHeight kStatusTableViewCellAvatarWidth
#define kStatusTableViewCellUserNameFontSize 14
#define kStatusTableViewCellMbTypeWidth 13 //會員圖標寬度
#define kStatusTableViewCellMbTypeHeight kStatusTableViewCellMbTypeWidth
#define kStatusTableViewCellCreateAtFontSize 12
#define kStatusTableViewCellSourceFontSize 12
#define kStatusTableViewCellTextFontSize 14


@interface KCStatusTableViewCell(){
    UIImageView *_avatar;//頭像
    UIImageView *_mbType;//會員類型
    UILabel *_userName;
    UILabel *_createAt;
    UILabel *_source;
    UILabel *_text;
}

@end

@implementation KCStatusTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self initSubView];
    }
    return self;
}

#pragma mark 初始化視圖
-(void)initSubView{
    //頭像控件
    _avatar=[[UIImageView alloc]init];
    [self.contentView addSubview:_avatar];
    //用戶名
    _userName=[[UILabel alloc]init];
    _userName.textColor=kStatusGrayColor;
    _userName.font=[UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize];
    [self.contentView addSubview:_userName];
    //會員類型
    _mbType=[[UIImageView alloc]init];
    [self.contentView addSubview:_mbType];
    //日期
    _createAt=[[UILabel alloc]init];
    _createAt.textColor=kStatusLightGrayColor;
    _createAt.font=[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize];
    [self.contentView addSubview:_createAt];
    //設備
    _source=[[UILabel alloc]init];
    _source.textColor=kStatusLightGrayColor;
    _source.font=[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize];
    [self.contentView addSubview:_source];
    //內容
    _text=[[UILabel alloc]init];
    _text.textColor=kStatusGrayColor;
    _text.font=[UIFont systemFontOfSize:kStatusTableViewCellTextFontSize];
    _text.numberOfLines=0;
//    _text.lineBreakMode=NSLineBreakByWordWrapping;
    [self.contentView addSubview:_text];
}

#pragma mark 設置微博
-(void)setStatus:(KCStatus *)status{
    //設置頭像大小和位置
    CGFloat avatarX=10,avatarY=10;
    CGRect avatarRect=CGRectMake(avatarX, avatarY, kStatusTableViewCellAvatarWidth, kStatusTableViewCellAvatarHeight);
    _avatar.image=[UIImage imageNamed:status.profileImageUrl];
    _avatar.frame=avatarRect;
    
    
    //設置會員圖標大小和位置
    CGFloat userNameX= CGRectGetMaxX(_avatar.frame)+kStatusTableViewCellControlSpacing ;
    CGFloat userNameY=avatarY;
    //根據文本內容取得文本占用空間大小
    CGSize userNameSize=[status.userName sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize]}];
    CGRect userNameRect=CGRectMake(userNameX, userNameY, userNameSize.width,userNameSize.height);
    _userName.text=status.userName;
    _userName.frame=userNameRect;
    
    
    //設置會員圖標大小和位置
    CGFloat mbTypeX=CGRectGetMaxX(_userName.frame)+kStatusTableViewCellControlSpacing;
    CGFloat mbTypeY=avatarY;
    CGRect mbTypeRect=CGRectMake(mbTypeX, mbTypeY, kStatusTableViewCellMbTypeWidth, kStatusTableViewCellMbTypeHeight);
    _mbType.image=[UIImage imageNamed:status.mbtype];
    _mbType.frame=mbTypeRect;
    
    
    //設置發佈日期大小和位置
    CGSize createAtSize=[status.createdAt sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize]}];
    CGFloat createAtX=userNameX;
    CGFloat createAtY=CGRectGetMaxY(_avatar.frame)-createAtSize.height;
    CGRect createAtRect=CGRectMake(createAtX, createAtY, createAtSize.width, createAtSize.height);
    _createAt.text=status.createdAt;
    _createAt.frame=createAtRect;
    
    
    //設置設備信息大小和位置
    CGSize sourceSize=[status.source sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize]}];
    CGFloat sourceX=CGRectGetMaxX(_createAt.frame)+kStatusTableViewCellControlSpacing;
    CGFloat sourceY=createAtY;
    CGRect sourceRect=CGRectMake(sourceX, sourceY, sourceSize.width,sourceSize.height);
    _source.text=status.source;
    _source.frame=sourceRect;
    
    
    //設置微博內容大小和位置
    CGFloat textX=avatarX;
    CGFloat textY=CGRectGetMaxY(_avatar.frame)+kStatusTableViewCellControlSpacing;
    CGFloat textWidth=self.frame.size.width-kStatusTableViewCellControlSpacing*2;
    CGSize textSize=[status.text boundingRectWithSize:CGSizeMake(textWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellTextFontSize]} context:nil].size;
    CGRect textRect=CGRectMake(textX, textY, textSize.width, textSize.height);
    _text.text=status.text;
    _text.frame=textRect;
    
    _height=CGRectGetMaxY(_text.frame)+kStatusTableViewCellControlSpacing;
}

#pragma mark 重寫選擇事件,取消選中
-(void)setSelected:(BOOL)selected animated:(BOOL)animated{
    
}
@end

這是我們自定義Cell這個例子的核心,自定義Cell分為兩個步驟:首先要進行各種控件的初始化工作,這個過程中隻要將控件放到Cell的View中同時設置控件顯示內容的格式(字體大小、顏色等)即可;然後在數據對象設置方法中進行各個控件的佈局(大小、位置)。在代碼中有幾點需要重點提示大傢:

對於單行文本數據的顯示調用– (CGSize)sizeWithAttributes:(NSDictionary *)attrs;方法來得到文本寬度和高度。對於多行文本數據的顯示調用– (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context ;方法來得到文本寬度和高度;同時註意在此之前需要設置文本控件的numberOfLines屬性為0。通常我們會在自定義Cell中設置一個高度屬性,用於外界方法調用,因為Cell內部設置Cell的高度是沒有用的,UITableViewCell在初始化時會重新設置高度。

3.最後我們看一下自定義Cell的使用過程:

KCStatusViewController.m

//
//  KCCutomCellViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatusCellViewController.h"
#import "KCStatus.h"
#import "KCStatusTableViewCell.h"

@interface KCStatusCellViewController (){
    UITableView *_tableView;
    NSMutableArray *_status;
    NSMutableArray *_statusCells;//存儲cell,用於計算高度
}
@end

@implementation KCStatusCellViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //創建一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,註意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    NSString *path=[[NSBundle mainBundle] pathForResource:@"StatusInfo" ofType:@"plist"];
    NSArray *array=[NSArray arrayWithContentsOfFile:path];
    _status=[[NSMutableArray alloc]init];
    _statusCells=[[NSMutableArray alloc]init];
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [_status addObject:[KCStatus statusWithDictionary:obj]];
        KCStatusTableViewCell *cell=[[KCStatusTableViewCell alloc]init];
        [_statusCells addObject:cell];
    }];
}
#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    return _status.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    KCStatusTableViewCell *cell;
    cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if(!cell){
        cell=[[KCStatusTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }
    //在此設置微博,以便重新佈局
    KCStatus *status=_status[indexPath.row];
    cell.status=status;
    return cell;
}

#pragma mark - 代理方法
#pragma mark 重新設置單元格高度
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    //KCStatusTableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
    KCStatusTableViewCell *cell= _statusCells[indexPath.row];
    cell.status=_status[indexPath.row];
    return cell.height;
}

#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}
@end

這個類中需要重點強調一下:Cell的高度需要重新設置(前面說過無論Cell內部設置多高都沒有用,需要重新設置),這裡采用的方法是首先創建對應的Cell,然後在– (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;方法中設置微博數據計算高度通知UITableView。

最後我們看一下運行的效果:

RunEffect2

常用操作

UITableView和UITableViewCell提供瞭強大的操作功能,這一節中會重點討論刪除、增加、排序等操作。為瞭方便演示我們還是在之前的通訊錄的基礎上演示,在此之前先來給視圖控制器添加一個工具條,在工具條左側放一個刪除按鈕,右側放一個添加按鈕:

#pragma mark 添加工具欄
-(void)addToolbar{
    CGRect frame=self.view.frame;
    _toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, kContactToolbarHeight)];
    //    _toolbar.backgroundColor=[UIColor colorWithHue:246/255.0 saturation:246/255.0 brightness:246/255.0 alpha:1];
    [self.view addSubview:_toolbar];
    UIBarButtonItem *removeButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(remove)];
    UIBarButtonItem *flexibleButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    UIBarButtonItem *addButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)];
    NSArray *buttonArray=[NSArray arrayWithObjects:removeButton,flexibleButton,addButton, nil];
    _toolbar.items=buttonArray;
}

1.刪除

在UITableView中無論是刪除操作還是添加操作都是通過修改UITableView的編輯狀態來改變的(除非你不用UITableView自帶的刪除功能)。在刪除按鈕中我們設置UITableView的編輯狀態:

#pragma mark 刪除
-(void)remove{
    //直接通過下面的方法設置編輯狀態沒有動畫
    //_tableView.editing=!_tableView.isEditing;
    
    [_tableView setEditing:!_tableView.isEditing animated:true];
}

點擊刪除按鈕會在Cell的左側顯示刪除按鈕:

UITableViewCellRemoveStatus

此時點擊左側刪除圖標右側出現刪除:

UITableViewCellDeleteStatus2

用過iOS的朋友都知道,一般這種Cell如果向左滑動右側就會出現刪除按鈕直接刪除就可以瞭。其實實現這個功能隻要實現代理-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;方法,隻要實現瞭此方法向左滑動就會顯示刪除按鈕。隻要點擊刪除按鈕這個方法就會調用,但是需要註意的是無論是刪除還是添加都是執行這個方法,隻是第二個參數類型不同。下面看一下具體的刪除實現:

#pragma mark 刪除操作
//實現瞭此方法向左滑動就會顯示刪除按鈕
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    KCContactGroup *group =_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    if (editingStyle==UITableViewCellEditingStyleDelete) {
        [group.contacts removeObject:contact];
        //考慮到性能這裡不建議使用reloadData
        //[tableView reloadData];
        //使用下面的方法既可以局部刷新又有動畫效果
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
        
        //如果當前組中沒有數據則移除組刷新整個表格
        if (group.contacts.count==0) {
            [_contacts removeObject:group];
            [tableView reloadData];
        }
    }
}

從這段代碼我們再次看到瞭MVC的思想,要修改UI先修改數據。而且我們看到瞭另一個刷新表格的方法– (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;,使用這個方法可以再刪除之後刷新對應的單元格。效果如下:

UITableViewCellDelete

2.添加

添加和刪除操作都是設置UITableView的編輯狀態,具體是添加還是刪除需要根據代理方法-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;的返回值來確定。因此這裡我們定義一個變量來記錄點擊瞭哪個按鈕,根據點擊按鈕的不同在這個方法中返回不同的值。

#pragma mark 取得當前操作狀態,根據不同的狀態左側出現不同的操作按鈕
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (_isInsert) {
        return UITableViewCellEditingStyleInsert;
    }
    return UITableViewCellEditingStyleDelete;
}
#pragma mark 編輯操作(刪除或添加)
//實現瞭此方法向左滑動就會顯示刪除(或添加)圖標
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    KCContactGroup *group =_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    if (editingStyle==UITableViewCellEditingStyleDelete) {
        [group.contacts removeObject:contact];
        //考慮到性能這裡不建議使用reloadData
        //[tableView reloadData];
        //使用下面的方法既可以局部刷新又有動畫效果
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
        
        //如果當前組中沒有數據則移除組刷新整個表格
        if (group.contacts.count==0) {
            [_contacts removeObject:group];
            [tableView reloadData];
        }
    }else if(editingStyle==UITableViewCellEditingStyleInsert){
        KCContact *newContact=[[KCContact alloc]init];
        newContact.firstName=@"first";
        newContact.lastName=@"last";
        newContact.phoneNumber=@"12345678901";
        [group.contacts insertObject:newContact atIndex:indexPath.row];
        [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];//註意這裡沒有使用reladData刷新
    }
}

運行效果:

UITableViewCellInsert

3.排序

隻要實現-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;代理方法當UITableView處於編輯狀態時就可以排序。

#pragma mark 排序
//隻要實現這個方法在編輯狀態右側就有排序圖標
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
    KCContactGroup *sourceGroup =_contacts[sourceIndexPath.section];
    KCContact *sourceContact=sourceGroup.contacts[sourceIndexPath.row];
    KCContactGroup *destinationGroup =_contacts[destinationIndexPath.section];
    
    [sourceGroup.contacts removeObject:sourceContact];
    if(sourceGroup.contacts.count==0){
        [_contacts removeObject:sourceGroup];
        [tableView reloadData];
    }
    
    [destinationGroup.contacts insertObject:sourceContact atIndex:destinationIndexPath.row];
    
}

運行效果:

UITableViewCellMove

最後給大傢附上上面幾種操作的完整代碼:

//
//  KCContactViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"
#define kContactToolbarHeight 44

@interface KCContactViewController (){
    UITableView *_tableView;
    UIToolbar *_toolbar;
    NSMutableArray *_contacts;//聯系人模型
    NSIndexPath *_selectedIndexPath;//當前選中的組和行
    BOOL _isInsert;//記錄是點擊瞭插入還是刪除按鈕
}

@end

@implementation KCContactViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //創建一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    _tableView.contentInset=UIEdgeInsetsMake(kContactToolbarHeight, 0, 0, 0);
    [self.view addSubview:_tableView];
    
    //添加工具欄
    [self addToolbar];
    
    //設置數據源,註意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    
    
    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];
    
    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];
    
}

#pragma mark 添加工具欄
-(void)addToolbar{
    CGRect frame=self.view.frame;
    _toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, kContactToolbarHeight)];
    //    _toolbar.backgroundColor=[UIColor colorWithHue:246/255.0 saturation:246/255.0 brightness:246/255.0 alpha:1];
    [self.view addSubview:_toolbar];
    UIBarButtonItem *removeButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(remove)];
    UIBarButtonItem *flexibleButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    UIBarButtonItem *addButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)];
    NSArray *buttonArray=[NSArray arrayWithObjects:removeButton,flexibleButton,addButton, nil];
    _toolbar.items=buttonArray;
}
#pragma mark 刪除
-(void)remove{
    //直接通過下面的方法設置編輯狀態沒有動畫
    //_tableView.editing=!_tableView.isEditing;
    _isInsert=false;
    [_tableView setEditing:!_tableView.isEditing animated:true];
}
#pragma mark 添加
-(void)add{
    _isInsert=true;
    [_tableView setEditing:!_tableView.isEditing animated:true];
}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄瞭組和行信息
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];

    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";

    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //如果緩存池沒有取到則重新創建並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    
    return cell;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 編輯操作(刪除或添加)
//實現瞭此方法向左滑動就會顯示刪除(或添加)圖標
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    KCContactGroup *group =_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    if (editingStyle==UITableViewCellEditingStyleDelete) {
        [group.contacts removeObject:contact];
        //考慮到性能這裡不建議使用reloadData
        //[tableView reloadData];
        //使用下面的方法既可以局部刷新又有動畫效果
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
        
        //如果當前組中沒有數據則移除組刷新整個表格
        if (group.contacts.count==0) {
            [_contacts removeObject:group];
            [tableView reloadData];
        }
    }else if(editingStyle==UITableViewCellEditingStyleInsert){
        KCContact *newContact=[[KCContact alloc]init];
        newContact.firstName=@"first";
        newContact.lastName=@"last";
        newContact.phoneNumber=@"12345678901";
        [group.contacts insertObject:newContact atIndex:indexPath.row];
        [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];//註意這裡沒有使用reladData刷新
    }
}

#pragma mark 排序
//隻要實現這個方法在編輯狀態右側就有排序圖標
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
    KCContactGroup *sourceGroup =_contacts[sourceIndexPath.section];
    KCContact *sourceContact=sourceGroup.contacts[sourceIndexPath.row];
    KCContactGroup *destinationGroup =_contacts[destinationIndexPath.section];
    
    [sourceGroup.contacts removeObject:sourceContact];
    [destinationGroup.contacts insertObject:sourceContact atIndex:destinationIndexPath.row];
    if(sourceGroup.contacts.count==0){
        [_contacts removeObject:sourceGroup];
        [tableView reloadData];
    }
}

#pragma mark 取得當前操作狀態,根據不同的狀態左側出現不同的操作按鈕
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (_isInsert) {
        return UITableViewCellEditingStyleInsert;
    }
    return UITableViewCellEditingStyleDelete;
}

#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

@end

通過前面的演示這裡簡單總結一些UITableView的刷新方法:

– (void)reloadData;刷新整個表格。

– (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);刷新指定的分組和行。

– (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);刷新指定的分組。

– (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;刪除時刷新指定的行數據。

– (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;添加時刷新指定的行數據。

UITableViewController

很多時候一個UIViewController中隻有一個UITableView,因此蘋果官方為瞭方便大傢開發直接提供瞭一個UITableViewController,這個控制器 UITableViewController實現瞭UITableView數據源和代理協議,內部定義瞭一個tableView屬性供外部訪問,同時自動鋪滿整個屏幕、自動伸縮以方便我們的開發。當然UITableViewController也並不是簡單的幫我們定義完UITableView並且設置瞭數據源、代理而已,它還有其他強大的功能,例如刷新控件、滾動過程中固定分組標題等。

有時候一個表格中的數據特別多,檢索起來就顯得麻煩,這個時候可以實現一個搜索功能幫助用戶查找數據,其實搜索的原理很簡單:修改模型、刷新表格。下面使用UITableViewController簡單演示一下這個功能:

//
//  KCContactTableViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactTableViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"
#define kSearchbarHeight 44

@interface KCContactTableViewController (){
    UITableView *_tableView;
    UISearchBar *_searchBar;
    //UISearchDisplayController *_searchDisplayController;
    NSMutableArray *_contacts;//聯系人模型
    NSMutableArray *_searchContacts;//符合條件的搜索聯系人
    BOOL _isSearching;
}
@end

@implementation KCContactTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化數據
    [self initData];
    
    //添加搜索框
    [self addSearchBar];

}

#pragma mark - 數據源方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if (_isSearching) {
        return 1;
    }
    return _contacts.count;;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (_isSearching) {
        return _searchContacts.count;
    }
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    KCContact *contact=nil;
    
    if (_isSearching) {
        contact=_searchContacts[indexPath.row];
    }else{
        KCContactGroup *group=_contacts[indexPath.section];
        contact=group.contacts[indexPath.row];
    }
    
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    
    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //如果緩存池沒有取到則重新創建並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    
    return cell;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    KCContactGroup *group=_contacts[section];
    return group.name;
}


#pragma mark - 搜索框代理
#pragma mark  取消搜索
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar{
    _isSearching=NO;
    _searchBar.text=@"";
    [self.tableView reloadData];
}

#pragma mark 輸入搜索關鍵字
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
    if([_searchBar.text isEqual:@""]){
        _isSearching=NO;
        [self.tableView reloadData];
        return;
    }
    [self searchDataWithKeyWord:_searchBar.text];
}

#pragma mark 點擊虛擬鍵盤上的搜索時
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
    
    [self searchDataWithKeyWord:_searchBar.text];
    
    [_searchBar resignFirstResponder];//放棄第一響應者對象,關閉虛擬鍵盤
}




#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    
    
    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];
    
    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];
    
}

#pragma mark 搜索形成新數據
-(void)searchDataWithKeyWord:(NSString *)keyWord{
    _isSearching=YES;
    _searchContacts=[NSMutableArray array];
    [_contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        KCContactGroup *group=obj;
        [group.contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            KCContact *contact=obj;
            if ([contact.firstName.uppercaseString containsString:keyWord.uppercaseString]||[contact.lastName.uppercaseString containsString:keyWord.uppercaseString]||[contact.phoneNumber containsString:keyWord]) {
                [_searchContacts addObject:contact];
            }
        }];
    }];
    
    //刷新表格
    [self.tableView reloadData];
}

#pragma mark 添加搜索欄
-(void)addSearchBar{
    CGRect searchBarRect=CGRectMake(0, 0, self.view.frame.size.width, kSearchbarHeight);
    _searchBar=[[UISearchBar alloc]initWithFrame:searchBarRect];
    _searchBar.placeholder=@"Please input key word...";
    //_searchBar.keyboardType=UIKeyboardTypeAlphabet;//鍵盤類型
    //_searchBar.autocorrectionType=UITextAutocorrectionTypeNo;//自動糾錯類型
    //_searchBar.autocapitalizationType=UITextAutocapitalizationTypeNone;//哪一次shitf被自動按下
    _searchBar.showsCancelButton=YES;//顯示取消按鈕
    //添加搜索框到頁眉位置
    _searchBar.delegate=self;
    self.tableView.tableHeaderView=_searchBar;
}

@end

運行效果:

UITableViewSearch

在上面的搜索中除瞭使用一個_contacts變量去保存聯系人數據還專門定義瞭一個_searchContact變量用於保存搜索的結果。在輸入搜索關鍵字時我們刷新瞭表格,此時會調用表格的數據源方法,在這個方法中我們根據定義的搜索狀態去決定顯示原始數據還是搜索結果。

我們發現每次搜索完後都需要手動刷新表格來顯示搜索結果,而且當沒有搜索關鍵字的時候還需要將當前的tableView重新設置為初始狀態。也就是這個過程中我們要用一個tableView顯示兩種狀態的不同數據,自然會提高程序邏輯復雜度。為瞭簡化這個過程,我們可以使用UISearchDisplayController,UISearchDisplayController內部也有一個UITableView類型的對象searchResultsTableView,如果我們設置它的數據源代理為當前控制器,那麼它完全可以像UITableView一樣加載數據。同時它本身也有搜索監聽的方法,我們不必在監聽UISearchBar輸入內容,直接使用它的方法即可自動刷新其內部表格。為瞭和前面的方法對比在下面的代碼中沒有直接刪除原來的方式而是註釋瞭對應代碼大傢可以對照學習:

//
//  KCContactTableViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactTableViewControllerWithUISearchDisplayController.h"
#import "KCContact.h"
#import "KCContactGroup.h"
#define kSearchbarHeight 44

@interface KCContactTableViewControllerWithUISearchDisplayController (){
    UITableView *_tableView;
    UISearchBar *_searchBar;
    UISearchDisplayController *_searchDisplayController;
    NSMutableArray *_contacts;//聯系人模型
    NSMutableArray *_searchContacts;//符合條件的搜索聯系人
    //BOOL _isSearching;
}
@end

@implementation KCContactTableViewControllerWithUISearchDisplayController

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化數據
    [self initData];
    
    //添加搜索框
    [self addSearchBar];

}

#pragma mark - 數據源方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
//    if (_isSearching) {
//        return 1;
//    }
    //如果當前是UISearchDisplayController內部的tableView則不分組
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        return 1;
    }
    return _contacts.count;;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//    if (_isSearching) {
//        return _searchContacts.count;
//    }
    //如果當前是UISearchDisplayController內部的tableView則使用搜索數據
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        return _searchContacts.count;
    }
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    KCContact *contact=nil;
    
//    if (_isSearching) {
//        contact=_searchContacts[indexPath.row];
//    }else{
//        KCContactGroup *group=_contacts[indexPath.section];
//        contact=group.contacts[indexPath.row];
//    }
    //如果當前是UISearchDisplayController內部的tableView則使用搜索數據
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        contact=_searchContacts[indexPath.row];
    }else{
        KCContactGroup *group=_contacts[indexPath.section];
        contact=group.contacts[indexPath.row];
    }
    
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    
    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //如果緩存池沒有取到則重新創建並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    
    return cell;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        return @"搜索結果";
    }
    KCContactGroup *group=_contacts[section];
    return group.name;
}
#pragma mark 選中之前
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [_searchBar resignFirstResponder];//退出鍵盤
    return indexPath;
}


#pragma mark - 搜索框代理
//#pragma mark  取消搜索
//-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar{
//    //_isSearching=NO;
//    _searchBar.text=@"";
//    //[self.tableView reloadData];
//    [_searchBar resignFirstResponder];
//}
//
//#pragma mark 輸入搜索關鍵字
//-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//    if([_searchBar.text isEqual:@""]){
//        //_isSearching=NO;
//        //[self.tableView reloadData];
//        return;
//    }
//    [self searchDataWithKeyWord:_searchBar.text];
//}

//#pragma mark 點擊虛擬鍵盤上的搜索時
//-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
//    
//    [self searchDataWithKeyWord:_searchBar.text];
//    
//    [_searchBar resignFirstResponder];//放棄第一響應者對象,關閉虛擬鍵盤
//}

#pragma mark - UISearchDisplayController代理方法
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
    [self searchDataWithKeyWord:searchString];
    return YES;
}


#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    
    
    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];
    
    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];
    
}

#pragma mark 搜索形成新數據
-(void)searchDataWithKeyWord:(NSString *)keyWord{
    //_isSearching=YES;
    _searchContacts=[NSMutableArray array];
    [_contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        KCContactGroup *group=obj;
        [group.contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            KCContact *contact=obj;
            if ([contact.firstName.uppercaseString containsString:keyWord.uppercaseString]||[contact.lastName.uppercaseString containsString:keyWord.uppercaseString]||[contact.phoneNumber containsString:keyWord]) {
                [_searchContacts addObject:contact];
            }
        }];
    }];
    
    //刷新表格
    //[self.tableView reloadData];
}

#pragma mark 添加搜索欄
-(void)addSearchBar{
    _searchBar=[[UISearchBar alloc]init];
    [_searchBar sizeToFit];//大小自適應容器
    _searchBar.placeholder=@"Please input key word...";
    _searchBar.autocapitalizationType=UITextAutocapitalizationTypeNone;
    _searchBar.showsCancelButton=YES;//顯示取消按鈕
    //添加搜索框到頁眉位置
    _searchBar.delegate=self;
    self.tableView.tableHeaderView=_searchBar;

    _searchDisplayController=[[UISearchDisplayController alloc]initWithSearchBar:_searchBar contentsController:self];
    _searchDisplayController.delegate=self;
    _searchDisplayController.searchResultsDataSource=self;
    _searchDisplayController.searchResultsDelegate=self;
    [_searchDisplayController setActive:NO animated:YES];

}

@end

運行效果:

UITableViewSearch2

註意如果使用Storyboard或xib方式創建上述代碼則無需定義UISearchDisplayController成員變量,因為每個UIViewController中已經有一個searchDisplayController對象。

MVC模式

通過UITableView的學習相信大傢對於iOS的MVC已經有一個大致的瞭解,這裡簡單的分析一下iOS中MVC模式的設計方式。在iOS中多數數據源視圖控件(View)都有一個dataSource屬性用於和控制器(Controller)交互,而數據來源我們一般會以數據模型(Model)的形式進行定義,View不直接和模型交互,而是通過Controller間接讀取數據。

就拿前面的聯系人應用舉例,UITableView作為視圖(View)並不能直接訪問模型Contact,它要顯示聯系人信息隻能通過控制器(Controller)來提供數據源方法。同樣的控制器本身就擁有視圖控件,可以操作視圖,也就是說視圖和控制器之間可以互相訪問。而模型既不能訪問視圖也不能訪問控制器。具體依賴關系如下圖:

MVC

You May Also Like