1 創建一個UITableViewController並展示簡單數據
1.1 問題
有很多移動客戶端的應用都是采用表的形式來展示數據,因為表視圖能使數據看起來更規整、更有調理,比如微信界面就是使用的表視圖,如圖-1所示:

圖-1
在IOS中表視圖是非常重要的視圖,類型名稱為UITabelViewController,是UIViewController的子類,本案例將學習如何使用UITableViewController來展示簡單的數據,完成效果如圖-2所示:

圖-2
1.2 方案
首先創建一個SingleViewApplication項目,創建一個TRMyTableViewController類繼承至UITableViewController,在TRAppDelegate的程序入口方法里面創建一個TRMyTableViewController對象做為根視圖控制器。
UITableViewController類有一個UITabelView類型的屬性tableView,該屬性就是表視圖控制器管理的表視圖,類似UIViewController和屬性view的關系。表視圖由以下幾個部分組成:
表頭視圖tableHeaderView:表視圖最上邊的視圖,用於展示表視圖的信息;
表腳視圖tableFooterView:表視圖最下邊的視圖;
單元格cell:是一個UITableViewCell對象,表視圖每一行的單位視圖;
分區section:由多個單元格cell組成,有分區頭和分區腳。
表視圖有兩個委托協議一個是UITableViewDataSource用於給表視圖加載數據,另一個是UITableViewDelegate用於實現其他事件的委托。
另外表視圖還有兩個屬性id <UITableViewDataSource>類型的dataSource和id <UITableViewDelegate>類型的delegate,分別用於指定加載數據的對象和被委托對象。
然后在TRMyTableViewController類里面通過實現的UITableViewDataSource協議的方法給表視圖加載簡單的數據。
1.3 步驟
實現此案例需要按照如下步驟進行。
步驟一:創建TRMyTableViewController對象做為程序的根視圖控制器
首先創建一個單視圖應用項目命名為TRMyTableViewController,再創建一個帶有xib的TRMyTableViewController類繼承至UITableViewController,生成的xib文件如圖-3所示:

圖-3
然后在TRAppDelegate.m的程序入口方法中創建一個TRMyTableViewController對象做為程序的根視圖,代碼如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- //創建一個TRTableViewController對象做為根視圖控制器
- TRMyTableViewController *myTVC = [[TRMyTableViewController alloc]initWithNibName:@"TRMyTableViewController" bundle:nil];
- self.window.rootViewController = myTVC;
- [self.window makeKeyAndVisible];
- return YES;
- }
在這里myTVC視圖控制器對象管理的表視圖就是程序展示的第一個界面視圖。
步驟二:實現UITableViewDataSource協議的方法給表視圖加載數據
UITabelViewController類遵守了兩個協議一個是UITableViewDataSource用於給表視圖加載數據,另一個是UITableViewDelegate用於實現委托,這里先學習通過實現UITableViewDataSource協議的方法來給表視圖加載數據。
系統會自動將TRMyTableViewController指定為它所管理的的表視圖的dataSource和delegate,可以在xib中查看,選中tableView點擊右鍵,出現如圖-4所示的窗口:

圖-4
從圖可見dataSource和delegate都是和File’s Owner關聯上的,此時的File’s Owner即TRMyTableViewController。
TRMyTableViewController類是UITabelViewController的子類,所以TRMyTableViewController也遵守了以上兩個協議,不用再額外寫遵守協議代碼,直接實現協議里面的方法即可,這里需要實現如下三個協議方法:
numberOfSectionsInTableView:可選方法,用於告訴表視圖需要顯示多少分區,不實現的時候默認返回1個分區;
tableView:numberOfRowsInSection:必須實現的方法,用於告訴表視圖每組需要顯示多少行;
tableView:cellForRowAtIndexPath:必須實現的方法,用於告訴表視圖每一行顯示的內容;
在這里先指定表視圖顯示1組,每組顯示20行,代碼如下所示:
- -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 1;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return 20;
- }
每一行用於顯示內容的是一個UITableViewCell類型的對象,該方法里面需要先創建一個UITableViewCell類型的對象cell,使用初始化方法initWithStyle: reuseIdentifier:進行初始化,style參數是cell的樣式,這里選擇系統默認的樣式即可,reuseIdentifier參數傳遞一個字符串@”Cell”,本案例先不對該參數的作用進行闡述,后面的案例會進行解釋。
UITableViewCell有一個UILabel類型的屬性textLabel,該屬性用來展示文本內容,這里設置cell的textLabel的顯示內容,最后將cell對象返回,代碼如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //創建cell對象
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
- //設置cell的顯示內容
- cell.textLabel.text = @"Hello World.";
- return cell;
- }
1.4 完整代碼
本案例中,TRAppDelegate.m文件中的完整代碼如下所示:
- #import "TRAppDelegate.h"
- #import "TRMyTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRMyTableViewController *myTVC = [[TRMyTableViewController alloc]initWithNibName:@"TRMyTableViewController" bundle:nil];
- self.window.rootViewController = myTVC;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRMyFirstViewController.m文件中的完整代碼如下所示:
- #import "TRMyTableViewController.h"
- @implementation TRMyTableViewController
- #pragma mark - Table view data source
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 1;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return 20;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
- cell.textLabel.text = @"Hello World.";
- return cell;
- }
- @end
2 使用IndexPath中的兩個屬性區別分區展示數據
2.1 問題
上一個案例已經學習了如何使用表視圖控制器展現一組簡單的數據,本案例將在上一個案例的基礎上讓表視圖以多個分區的形式展示數據,如圖-5所示:

圖-5
2.2 方案
首先同上一個案例一樣創建一個表視圖控制器TRMytableViewController作為本應用的根視圖控制器。
其次通過實現UITableViewDataSource協議的方法告訴tableView有多少分區、有多少行以及每一行的顯示內容。
2.3 步驟
實現此案例需要按照如下步驟進行。
步驟一:創建TRMyTableViewController對象做為程序的根視圖控制器
首先同第一個案例一樣創建一個單視圖應用項目命名為TRMyTableViewController,再創建一個帶有xib的TRMyTableViewController類繼承至UITableViewController。
步驟二:實現UITableViewDataSource協議的方法給表視圖加載數據
NSIndexPath用來管理存儲路徑,有兩個屬性section和row,section用來描述分區,row用來描述分區里面的某一行。
首先通過實現numberOfSectionsInTableView:方法讓tabelView分成三個區,該方法不實現默認是返回一個分區,代碼如下所示:
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 3;
- }
然后通過tableView:numberOfRowsInSection:方法確定每個分區的行數,代碼如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- if(section == 0) return 2;
- else if(section == 1) return 3;
- else if(section == 2) return 1500;
- return 0;
- }
最后通過實現tableView:cellForRowAtIndexPath:確定不同分區的每一行的顯示內容,本案例創建cell選擇的是UITableViewCellStyleSubtitl類型,該類型除了顯示textLabel的顯示內容,還可以設置cell的另外兩個屬性,如下所示:
imageView屬性:用來顯示一張圖片;
detailTextLabel屬性:用來顯示詳細信息。
代碼如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"];
- if(indexPath.section == 0){
- cell.textLabel.text = [NSString stringWithFormat:@"第0區的第%d行",indexPath.row];
- }else{
- cell.textLabel.text = [NSString stringWithFormat:@"%d區%d行",indexPath.section,indexPath.row];
- }
- cell.imageView.image = [UIImage imageNamed:@"a.png"];
- cell.detailTextLabel.text = @"這是詳細信息";
- return cell;
- }
2.4 完整代碼
本案例中,TRAppDelegate.m文件中的完整代碼如下所示:
- #import "TRAppDelegate.h"
- #import "TRMyTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRMyTableViewController *myTVC = [[TRMyTableViewController alloc]initWithNibName:@"TRMyTableViewController" bundle:nil];
- self.window.rootViewController = myTVC;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRMyTableViewController.m文件中的完整代碼如下所示:
- #import "TRMyTableViewController.h"
- @implementation TRMyTableViewController
- #pragma mark - Table view data source
- //如果不實現此方法,默認為1個分區
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 3;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- if(section == 0) return 2;
- else if(section == 1) return 3;
- else if(section == 2) return 1500;
- return 0;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"];
- if(indexPath.section == 0){
- cell.textLabel.text = [NSString stringWithFormat:@"第0區的第%i行",indexPath.row];
- }else{
- cell.textLabel.text = [NSString stringWithFormat:@"%i區%i行",indexPath.section,indexPath.row];
- }
- cell.imageView.image = [UIImage imageNamed:@"a.png"];
- cell.detailTextLabel.text = @"這是詳細信息";
- return cell;
- }
- @end
3 展示Cell重用時的數據處理
3.1 問題
表視圖在展現數據的時候,超出界面的單元格cell對象並不會被釋放,而是放入了tableView的用於管理單元格的隊列中,所以需要在創建cell對象之前先試着從此隊列中去拿已經存在的cell對象,如果能拿到就不需要再創建cell對象,而是重用此Cell對象,以減少對內存的占用,本案例將演示cell的重用過程。
3.2 方案
UITableViewCell的重用機制:
1)當tableView在創建每一個cell的時候會對cell做一個重用標記,該標記就是初始化方法initWithStyle:reuseIdentifier:的reuseIdentifier參數,是一個NSString類型;
2)當表視圖的單元格超出界面之后會將該單元格加入的tableView的單元格隊列中,因此每次創建cell之前都會做一個判斷,判斷tableView的單元格隊列中是否存在cell對象,如果有就直接使用,如果沒有就創建一個新的cell對象。
3.3 步驟
實現此案例需要按照如下步驟進行。
步驟一:定義一個重用標記
在tableView:cellForRowAtIndexPath:方法里面首先創建一個NSString類型的對象cellIdentifier用做重用標記,該對象是一個static類型,保證在程序結束之前不會被銷毀,代碼如下所示:
- static NSString *cellIdentifier = @"MyCell";
步驟二:判斷tableView的單元格隊列中是否存在cell對象
通過tabelView的dequeueReusableCellWithIdentifier:方法獲取 cell對象,如果該方法返回的cell對象不為空,說明有可重用的cell,identifier參數就是上一步所創建的重用標記cellIdentifier,代碼如下所示:
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
步驟三:如果沒有可重用的cell則創建一個新的cell對象
通過上一步獲取到的cell對象如果為空,說明沒有可以重用的cell對象,則需要創建一個新的cell對象以供使用,代碼如下所示:
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
3.4 完整代碼
本案例中,TRTableViewController.m文件中的完整代碼如下所示:
- #import "TRTableViewController.h"
- @implementation TRTableViewController
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellIdentifier = @"MyCell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
- return cell;
- }
- @end
4 注冊Cell並重用
4.1 問題
本案例學習使用注冊cell的方式實現cell的重用。
4.2 方案
首先向tableView注冊一個重用的Cell類;
然后向tableView索要隊列中的Cell對象時,如果隊列中沒有Cell對象,tableView會自動創建一個並返回,這樣可以確保dequeue方法一定會返回一個Cell對象供使用。
4.3 步驟
實現此案例需要按照如下步驟進行。
步驟一:注冊cell
在TRTableViewController里面首先創建一個NSString類型的對象cellIdentifier用做重用標記,該對象是一個static類型,保證在程序結束之前不會被銷毀,代碼如下所示:
- static NSString *cellIdentifier = @"MyCell";
在viewDidLoad方法里面使用registerClass: forCellReuseIdentifier:方法注冊cell,代碼如下所示:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //注冊一個Cell類型到tableView
- [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:cellIdentifier];
- }
步驟二:創建cell對象
在tableView: cellForRowAtIndexPath:方法里面創建cell對象,使用dequeueReusableCellWithIdentifier: forIndexPath:方法創建cell對象時,如果沒有可重用的cell會自動創建一個,因此能保證返回一個cell對象,代碼如下所示:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //只要注冊過Cell類型,此處tableView保證能返回一個Cell對象
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
- return cell;
- }
4.4 完整代碼
- #import "TRTableViewController.h"
- @implementation TRTableViewController
- //定義cell的重用標識
- static NSString *cellIdentifier = @"MyCell";
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //注冊一個Cell類型到tableView
- [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:cellIdentifier];
- }
- //cellForRow方法里面的代碼
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //只要注冊過Cell類型,此處tableView保證能返回一個Cell對象
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
- cell.textLabel.text = self.data[indexPath.row];
- return cell;
- }
- @end
5 使用TableView展示數據列表
5.1 問題
為了使tableView顯示的數據更靈活,通常都會有一個NSArray類型的屬性來管理表視圖的數據,本案例在第三個案例(或者案例四)的基礎上讓表視圖展示一組字符串數據(城市名稱)。
5.2 方案
首先定義一個NSArray類型的屬性用於存儲表視圖的數據;
其次在TRAppDelegate的程序入口方法里面初始化數組;
最后在協議方法里面給表視圖加載數據。
5.3 步驟
實現此案例需要按照如下步驟進行。
步驟一:定義一個存儲數據的NSArray類型的屬性
為了使tableView顯示的數據更靈活,通常都會有一個NSArray類型的屬性來管理表視圖的數據,在TRTableViewController類里面定義一個公開的屬性NSArray類型的屬性data,代碼如下所示:
- @property (nonatomic, strong) NSArray *data;
步驟二:初始化存儲數據的數組
在TRAppdelegate的程序入口方法里面對該屬性進行初始化,這里給一個模擬數據,代碼如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRTableViewController *tvc = [[TRTableViewController alloc]initWithNibName:@"TRTableViewController" bundle:nil];
- tvc.data = @[@"北京", @"上海", @"廣州", @"深圳", @"濟南",@"石家庄",@"武漢",@"鄭州",@"成都",@"重慶",@"珠海",@"香港",@"澳門"];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:tvc];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
步驟三:給表視圖加載數據
首先在tableView:numberOfRowsInSection:方法里面返回表視圖的行數,通常都是返回一個數組的count,本案例即self.data.count,代碼如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.data.count;
- }
然后在TRTableViewController類里面的tableView:cellForRowAtIndexPath:的方法里面給表視圖加載數據,代碼如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellIdentifier = @"MyCell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
- cell.textLabel.text = self.data[indexPath.row];
- return cell;
- }
運行程序實現效果如圖-6所示:

圖-6
步驟四:點擊某一行單元格
在點擊表視圖某一行時會調用tableView:didSelectRowAtIndexPath:方法,該方法是UITabelViewDelegate協議里面的方法,因此只需在TRTableViewController.m文件中實現該方法即可,indexPath參數就是被點擊的單元格的路徑,代碼如下所示:
- //當用戶點擊了某一行Cell時被調用
- -(void)tableView:(UITableView *)tableView
- didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSLog(@"用戶點擊了%d區的%d行, %@", indexPath.section, indexPath.row, self.data[indexPath.row]);
- }
運行程序,點擊某一行可見控制台會輸出被點擊的地區名稱。
5.4 完整代碼
本案例中,TRAppDeleagte.m文件中的完整代碼如下所示:
- #import "TRAppDelegate.h"
- #import "TRTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRTableViewController *tvc = [[TRTableViewController alloc]initWithNibName:@"TRTableViewController" bundle:nil];
- tvc.data = @[@"北京", @"上海", @"廣州", @"深圳", @"濟南",@"石家庄",@"武漢",@"鄭州",@"成都",@"重慶",@"珠海",@"香港",@"澳門"];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:tvc];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRTableViewController.m文件中的完整代碼如下所示:
- #import "TRTableViewController.h"
- @implementation TRTableViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"地區";
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.data.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellIdentifier = @"MyCell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
- cell.textLabel.text = self.data[indexPath.row];
- return cell;
- }
- @end
6 項目友錄中的朋友列表
6.1 問題
模擬系統的通訊錄功能,使用表視圖控制器展示聯系人界面,並實現添加聯系人功能,本案例表視圖需要展示的是一組對象數據,實現效果如圖-7、圖-8所示:

圖-7

圖-8
6.2 方案
首先創建一個SingleViewApplication項目,再創建一個表視圖控制器類TRContactTableViewController,該類有一個管理聯系人的NSMutableArray類型的屬性contacts。
在TRAppDelegate的程序入口方法里面創建一個帶有導航的表視圖控制器做為根視圖控制器。
其次根據本案例的需求創建一個聯系人類TRCantact類,用於保存聯系人信息。
然后創建一個TRInputViewController類繼承至UIViewController,用於管理編輯聯系人界面。再給TRContactTableViewController的導航欄添加一個編輯按鈕,點擊按鈕跳轉到編輯界面,並實現表視圖數據源協議方法。
最后在TRInputViewController類里面根據輸入的信息創建TRContact對象,然后通過委托的方式將新建聯系人對象傳遞給TRContactTableViewController類,並回退到聯系人界面。TRContactTableViewController類里面通過實現協議方法給聯系人頁面加載和更新數據。
6.3 步驟
實現此案例需要按照如下步驟進行。
步驟一:創建項目,設置根視圖控制器
創建一個SingleViewApplication項目,然后再創建一個表視圖控制器類TRContactTableViewController,並且定義一個用來管理聯系人的NSMutableArray類型的屬性contacts,代碼如下所示:
- @interface TRContactTableViewController ()
- @property(nonatomic, strong) NSMutableArray *contacts;
- @end
- //重寫setter方法,初始化實例變量_contacts
- - (NSMutableArray *)contacts
- {
- if(!_contacts){
- _contacts = [@[] mutableCopy];
- }
- return _contacts;
- }
在TRAppDelegate的程序入口方法里面創建一個帶有導航的表視圖控制器做為根視圖控制器,代碼如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRContactTableViewController *contactTVC = [[TRContactTableViewController alloc]initWithNibName:@"TRContactTableViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:contactTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
步驟二:創建TRContact聯系人類
創建一個TRContact類繼承至NSObject,並且定義兩個NSString類型的屬性name和phoneNumber,分別用於存儲聯系人的姓名和手機號,代碼如下所示:
- @property (nonatomic,copy)NSString *name;
- @property (nonatomic,copy)NSString *phoneNumber;
然后自定義一個初始化方法initWithName:andPhoneNumber:,方便外部創建聯系人對象的時候進行初始化,代碼如下所示:
- -(instancetype)initWithName:(NSString*)name
- andPhoneNumber:(NSString*)phoneNumber
- {
- self = [super init];
- if (self) {
- self.name = name;
- self.phoneNumber = phoneNumber;
- }
- return self;
- }
步驟三:創建TRInputViewController類,並實現頁面跳轉
創建一個TRInputViewController類,用於管理編輯聯系人界面,在xib文件中拖放兩個輸入框(接受用戶輸入的聯系人姓名和手機號),兩個label和一個按鈕,並在檢查器中設置相關屬性,這里需要注意將用於接收用戶輸入手機號的輸入框,鍵盤類型選擇NumberPad類型,完成效果如圖-9所示:

圖-9
然后給TRContactTableViewController的導航欄添加標題和編輯按鈕,點擊按鈕實現頁面的跳轉,這里使用present的方式跳轉頁面,新頁面會帶有一個新的導航控制器,代碼如下所示:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //設置導航欄標題
- self.title = @"通訊錄";
- //給導航欄添加編輯按鈕,點擊按鈕會調用addContact:方法
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addContact:)];
- }
- //實現addContact方法
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
實現表視圖數據源協議方法,完成數據加載代碼,這里只有一個分區,因此可不用實現分區方法默認返回1,行數直接返回self.contacts.count,在tableView: cellForRowAtIndexPath:方法里,讓cell的textLabel顯示聯系人的姓名,cell的detailTextLabel顯示聯系人的手機號,代碼如下所示:
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return self.contacts.count;
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- //創建cell時,類型選擇UITableViewCellStyleValue1,右邊顯示detailTextLabel
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
- }
- //通過indexPath.row在self.contacts數組里面找到當前cell對應的聯系人對象
- TRContact *contact = self.contacts[indexPath.row];
- //cell.textLabel顯示聯系人姓名
- cell.textLabel.text = contact.name;
- //cell.detailTextLabel顯示聯系人的手機號
- cell.detailTextLabel.text = contact.phoneNumber;
- return cell;
- }
運行程序,界面效果如圖-10所示:

圖-10
從圖中可見目前聯系人頁面沒有任何信息,那是因為聯系人數組里面還沒有任何數據,下一步則是通過編輯頁面給聯系人數組添加數據。
步驟四:根據用戶的輸入創建聯系人對象並更新聯系人界面
將TRInputViewController界面上的兩個輸入框關聯成私有屬性nametextField和phoneNumberTextField,代碼如下所示:
- @property (weak, nonatomic) IBOutlet UITextField *nameTextField;
- @property (weak, nonatomic) IBOutlet UITextField *phoneNumberTextField;
將確定按鈕關聯成私有方法submit,該方法的功能主要是根據用戶輸入的信息創建一個新的TRContact對象contact,並通過委托將contact對象傳遞給聯系人界面,最后返回聯系人界面。
因此首先需要在TRInputViewController.h文件里面定義一個協議TRInputViewDelegate和一個委托屬性,代碼如下所示:
- @class TRInputViewController;
- @protocol TRInputViewDelegate <NSObject>
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact;
- @end
- @interface TRInputViewController : UIViewController
- //在創建TRInputViewController對象時對delegate屬性進行賦值確定被委托者
- @property (nonatomic, weak) id<TRInputViewDelegate> delegate;
- @end
然后實現submit方法,代碼如下所示:
- - (IBAction)submit
- {
- [self.phoneNumberTextField resignFirstResponder];
- //創建TRContact聯系人對象
- TRContact *contact = [[TRContact alloc]initWithName:self.nameTextField.text andPhoneNumber:self.phoneNumberTextField.text];
- //將contact對象傳遞給聯系人界面
- [self.delegate inputViewController:self contact:contact];
- [self dismissViewControllerAnimated:YES completion:nil];
- }
最后TRContactTableViewController遵守TRInputViewDelegate協議,並實現協議方法inputViewController:contact:,該方法里主要是根據傳遞過來的聯系人信息更新contacts數組和界面,這里使用reloadData方法刷新界面,代碼如下所示:
- //遵守協議
- @interface TRContactTableViewController () <TRInputViewDelegate>
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- //添加聯系人方法里面給inputVC.delegate賦值,指定當前對象為被委托者
- //添加聯系人方法里面給inputVC.delegate賦值,指定當前對象為被委托者
- inputVC.delegate = self;
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
- //實現TRInputViewDelegate協議方法
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact
- {
- //將傳遞過來的contact對象添加到聯系人數組
- [self.contacts addObject:contact];
- //重新加載數據,更新界面
- [self.tableView reloadData];
- }
6.4 完整代碼
本案例中,TRAppDelegate.m文件中的完整代碼如下所示:
- #import "TRAppDelegate.h"
- #import "TRContactTableViewController.h"
- @implementation TRAppDelegate
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRContactTableViewController *contactTVC = [[TRContactTableViewController alloc]initWithNibName:@"TRContactTableViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:contactTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRContactTableViewController.m文件中的完整代碼如下所示:
- #import "TRContactTableViewController.h"
- #import "TRInputViewController.h"
- @interface TRContactTableViewController () <TRInputViewDelegate>
- @property(nonatomic, strong) NSMutableArray *contacts;
- @end
- @implementation TRContactTableViewController
- - (NSMutableArray *)contacts
- {
- if(!_contacts){
- _contacts = [@[] mutableCopy];
- }
- return _contacts;
- }
- //實現TRInputViewDelegate協議方法
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact
- {
- [self.contacts addObject:contact];
- //重新加載數據
- [self.tableView reloadData];
- }
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"通訊錄";
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addContact:)];
- }
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- inputVC.delegate = self;
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.contacts.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
- }
- TRContact *contact = self.contacts[indexPath.row];
- cell.textLabel.text = contact.name;
- cell.detailTextLabel.text = contact.phoneNumber;
- return cell;
- }
- @end
本案例中,TRInputViewController.h文件中的完整代碼如下所示:
- #import <UIKit/UIKit.h>
- #import "TRContact.h"
- @class TRInputViewController;
- @protocol TRInputViewDelegate <NSObject>
- -(void)inputViewController:(TRInputViewController *)inputVC
- contact:(TRContact *)contact;
- @end
- @interface TRInputViewController : UIViewController
- @property (nonatomic, weak) id<TRInputViewDelegate> delegate;
- @end
本案例中,TRInputViewController.m文件中的完整代碼如下所示:
- #import "TRInputViewController.h"
- @interface TRInputViewController ()
- @property (weak, nonatomic) IBOutlet UITextField *nameTextField;
- @property (weak, nonatomic) IBOutlet UITextField *phoneNumberTextField;
- @end
- @implementation TRInputViewController
- - (IBAction)submit
- {
- [self.phoneNumberTextField resignFirstResponder];
- TRContact *contact = [[TRContact alloc]initWithName:self.nameTextField.text andPhoneNumber:self.phoneNumberTextField.text];
- [self.delegate inputViewController:self contact:contact];
- [self dismissViewControllerAnimated:YES completion:nil];
- }
- - (IBAction)next {
- [self.nameTextField resignFirstResponder];
- [self.phoneNumberTextField becomeFirstResponder];
- }
- @end
本案例中,TRContact.h文件中的完整代碼如下所示:
本案例中,TRContact.m文件中的完整代碼如下所示:
7 增加一個新朋友
7.1 問題
上一個案例已經實現的通訊錄的展示聯系人、添加聯系人功能,刷新界面的時候使用reloadData方法,本案例將使用另外一種局部更新界面的方式更新聯系人界面。
7.2 方案
本案例直接在上一個案例上做修改,主要是重寫TRContactTableViewController類里面更新界面的代碼,reloadData方法更新整個界面,也就是會將整個表視圖的數據重新加載一遍,本案例采用insertRowsAtIndexPath方法局部更新界面。
7.3 步驟
實現此案例需要按照如下步驟進行。
步驟一:使用insertRowsAtIndexPath方法局部更新界面
insertRowsAtIndexPath方法主要用於在表視圖里面插入一行單元格,indexPath參數是該單元格插入的位置,是一個NSIndexPath類型的參數,因此在inputViewController:contact:方法里面,首先同樣的將傳遞過來的contact添加到數組,然后找到插入單元格的位置,使用insertRowsAtIndexPath方法在表視圖里面插入單元格,代碼如下所示:
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact
- {
- [self.contacts addObject:contact];
- //創建一個NSIndexPath對象,插入單元格的位置
- NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.contacts.count - 1 inSection:0];
- //表視圖中插入單元格
- [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
- }
7.4 完整代碼
本案例中,TRContactTableViewController.m文件中的完整代碼如下所示:
- #import "TRContactTableViewController.h"
- #import "TRInputViewController.h"
- @interface TRContactTableViewController () <TRInputViewDelegate>
- @property(nonatomic, strong) NSMutableArray *contacts;
- @end
- @implementation TRContactTableViewController
- - (NSMutableArray *)contacts
- {
- if(!_contacts){
- _contacts = [@[] mutableCopy];
- }
- return _contacts;
- }
- //實現TRInputViewDelegate協議方法
- -(void)inputViewController:(TRInputViewController *)inputVC
- contact:(TRContact *)contact
- {
- [self.contacts addObject:contact];
- NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.contacts.count - 1 inSection:0];
- [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
- }
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"通訊錄";
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addContact:)];
- }
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- inputVC.delegate = self;
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.contacts.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
- }
- TRContact *contact = self.contacts[indexPath.row];
- cell.textLabel.text = contact.name;
- cell.detailTextLabel.text = contact.phoneNumber;
- return cell;
- }
- @end