2010年4月1日 星期四

iOS 開發教學 - 使用 UITableView & UITableViewController 提供表單服務

除了在 CS 193P iPhone Application Development 看到的教學外,另外還有到詳細的表單教學:iPhone Programming Tutorial: Part 6: Creating custom UITableViewCell Using Interface Builder [UITableView],除此之外,在文章下頭還有操作影片,非常詳盡喔!該篇文章主打教你怎樣透過 Interface Builder 去豐富 UITableViewCell 的樣貌。

只是我看著這種教學方式,感覺有點不實在,甚至照著模仿時,迷失自己。像是忘了物件沒初始化等等的,或是沒去看為啥沒顯示出東西,這些很根本的東西容易遺忘。

以下是筆記自己的操作流程:


  • [Xcode]->[Create a new Xcode project]->[iPhone OS]->[Application]->[Window-based Application]-> 填寫 MyTable 即可

  • 點選到 Class 裡頭(非必要動作,只是讓新增的檔案都在 Class 目錄裡),[Xcode]->[File]->[New File]->[Cocoa Touch Class]->[UIViewController subclass] 並且只勾選 UITableViewController ,取名為 MyTableViewController

如此一來,在 Class 中就會有:

MyTableAppDelegate.h
MyTableAppDelegate.m
MyTableViewController.h
MyTableViewController.m

接著依序先對 MyTableAppDelegate 設定,在對 MyTableViewController 處理:

MyTableAppDelegate.h

#import <UIKit/UIKit.h>
#import "MyTableViewController.h"

@interface MyTableAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    MyTableViewController *myTableController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

MyTableAppDelegate.m

#import "MyTableAppDelegate.h"

@implementation MyTableAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {  
    myTableController = [[MyTableViewController alloc] init]; // initWithStyle:UITableViewStylePlain
    [window addSubview:myTableController.view];
    // Override point for customization after application launch
    [window makeKeyAndVisible];
}

- (void)dealloc {
    [myTableController release];
    [window release];
    [super dealloc];
}

@end

MyTableViewController.h

#import <UIKit/UIKit.h>

@interface MyTableViewController : UITableViewController {
    NSArray *myData;
}

@end

MyTableViewController.m

#import "MyTableViewController.h"

@implementation MyTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    myData = [[NSArray alloc] initWithObjects:@"GMail" , @"YMail" , @"Hotmail" , @"NUMail" , nil];
  
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
  
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


#pragma mark Table view methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}


// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [myData count];
    //return 0;
}


// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  
    static NSString *CellIdentifier = @"Cell";
  
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Updated @ 2012-08-07
    // Sample Code without "cell check" message:
    // *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
    //
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
  
    // Set up the cell...
    cell.textLabel.text = (NSString*)[myData objectAtIndex:indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic may go here. Create and push another view controller.
    // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
    // [self.navigationController pushViewController:anotherViewController];
    // [anotherViewController release];
}

- (void)dealloc {
    [myData release];
    [super dealloc];
}

@end

執行成果:

UITableView

以上藍色字樣就是有增加或修改的地方,這是一個非常簡單的清單列表。重要的地方是在於 MVC 架構,在使用 UITableViewController 時,只需要去處理資料的來源(如 NSArray *myData),然後是回傳有多少東西(numberOfRowsInSection),以及顯示項目的處理 tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath ,只要把握住這三個關鍵點,一切就很正常啦!我一開始卡在一直沒東西出來,原因就是忘了去更新 numberOfRowsInSection 函數,它預設是 0 個東西。

熟悉最簡單的東西後,你可以更改 Table 呈現方式,即在 MyTableAppDelegate.m 初始化 myTableController 時,改成:

myTableController = [[MyTableViewController alloc] initWithStyle:UITableViewStyleGrouped]; // 預設為 UITableViewStylePlain

執行的成果:

UITableView Grouped

剩下的,可以去玩玩 Section 部分,讓顯示的清單有分群效果,或是讓 item 有事件,如點下後切換場景等等的。

UITableView Section
(此例只簡單將numberOfSectionsInTableView回傳數字改為2)

@2010/04/13 更新,以下是多個 Section 範例的部份程式碼:

MyTableViewController.h

@interface MyTableViewController : UITableViewController {
     NSMutableArray *dataSource;
}

MyTableViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    dataSource = [[NSMutableArray alloc] init];
  
    NSMutableArray* b1 = [[NSMutableArray alloc] init];
    [b1 addObject:@"1"];
    [b1 addObject:@"2"];
    [b1 addObject:@"3"];
  
    [dataSource addObject:b1];
   
[b1 release];

    NSMutableArray* b2 = [[NSMutableArray alloc] init];
    [b2 addObject:@"A"];
    [b2 addObject:@"B"];
    [b2 addObject:@"C"];
   
    [dataSource addObject:b2];

    [b2 release];

   
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if ( dataSource == nil )
        return 1;
    return [dataSource count];

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSInteger bucketCount = -1;
    NSObject *target_section;
    if ( dataSource == nil )
        return 0;
    if( ( bucketCount = [dataSource count] ) < 1 || bucketCount <= section || (target_section = [dataSource objectAtIndex:section ]) == nil )
        return 0;
    return [ (NSMutableArray*)target_section count ];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  
    static NSString *CellIdentifier = @"Cell";
  
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
  
    // Set up the cell...
    cell.textLabel.text = (NSString*)[ (NSMutableArray*)[dataSource objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
  
    return cell;
}

// 此函數預設沒有,要自己打一下
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    NSInteger bucketCount = -1;
    NSObject *target;
    if ( dataSource == nil )
        return @"";
    if( ( bucketCount = [dataSource count] ) < 1 || bucketCount <= section || ( target = [dataSource objectAtIndex:section ] ) == nil || (target = [ (NSMutableArray*)target objectAtIndex:0] ) == nil )
        return @"";
    return (NSString*) target;
}

Demo

UITableViewStyleGrouped

UITableViewStyleGrouped

UITableViewStylePlain

UITableViewStylePlain

最後,如果你希望你的 Section 可以透過 index 跳過去,如下圖中右邊的文字

sectionIndexTitlesForTableView

那就再替 MyTableViewController.m 增加一個函數,並且自定各個 section 要顯示的 title 囉!

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    NSArray* sIndex = [[NSArray alloc] initWithObjects:@"0-9", @"A", @"中", @"日", @"法", @"德", nil];
    return
[sIndex autorelease];
}

概念就只是依序點到哪個位置,直接切到該 section 的起頭。

參考資料:


1 則留言:

  1. 感謝~分享~ 這篇文章很有教學價值
    不過Apple產生的Sample code 並沒有

    if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    所以只注意看藍色的時候會一直發生問題

    版主回覆:(09/22/2012 02:51:41 AM)


    哇喔!今天也來練一下,才發現你的提醒 :D

    出現的錯誤訊息:*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'

    不知為何現在的 sample code 沒有這段 orz
    會不會因為現在都流行透過 interfacebuilder ,幫你把元件拉一拉呢?
    在 2010 年練習時,預設是會有這段程式碼的,這也是為何我沒對這段程式碼標色啦

    回覆刪除