2009-05-23

產生含有 Navigation controller 的 Tab bar controller

在 Xcode 新增專案時,會有六個常用的範本給我們加入,其中擺在第一個的 Navigation-based Application 大概是最常用的,像 iPhone 內建的程式【聯絡資訊】。
而如果要產生像內建程式【音樂】的,一開始要選 【Tab Bar Application】範本。
在我剛寫第一個 iPhone 程式時,我挑選的是 Navigation-based Application,但是當我要分頁的時候,發現有點困難,再回去看【View Controller Programming Guide for iPhone OS】的 Combining Tab Bar and Navigation Controllers 章節才發現原來 Navigation View 是要在 Tab Bar 裡面。
還好,透過 Interface Builder 的功能,可以稍作調整,不用重新來過。

我在這個範例介紹的,不是用 Xcode 內建的 Tab Bar Application 範本開始,而是直接從一個乾淨的 Window-based Application 加入 Tab Bar,再加入 Navigation。

結果會是這樣:
最後我還會進階把第二頁換成 TableView


準備好了嗎?那就開始吧!

首先,執行 Xcode 選 New Project\ Window-based Application
(我用的 Xcode 是 Version 3.1.3)

專案名稱 Tabs

Xcode 產生需要的檔案:
不過我通常會把 Classes 內的這幾個 Group 展開
4.點選 Resources 的 MainWindow.xib,開啓 Interbase Builder,這時候的主畫面只有這幾個
按下鍵盤 +SHIFT+L 開啓 Library 把 "Tab Bar Controller" 拖曳進來 MainWindow.xib 的視窗內

MainWindow.xib 的視窗會看到 Tab Bar Controller,把它展開看看
這裡有一個 UITabBarController + 一個 UITabBar + 兩個 UIViewController,等一下 Selected View (Item 1) 的要加入一般的 View Controller,第二個 View Controller (Item 2) 的要加入 Navigation Controller

畫面如下:
看到 Item 1 項目現在是選取狀態,所以有點藍黑色,你可以先把 Item 1 與 Item 2 各點兩下,分別改成【第一頁】與【第二頁】

記得在 MainWindow.xib 先存檔 +S


接著要做幾個連結的動作,先把 Tab Bar Controller 加入 Delegate 的 Outlet:
1.點選 MainWindow.xib 內的 Tabs App Delegate,按下 +SHIFT+I 開啓 Inspecter
2.在 Class Outlet 內按底下的 "+" 號,加入 Outlet: rootController,Type: UITabBarController
*註:這裡在新版的 Xcode 有些不同,我在最後補充(20101024)

3.接著在 MainWindow.xib 的視窗內,點選 Tabs App Delegate,然後按住 Control 不放,接著滑鼠按左鍵移動一下,看到藍色拖曳線就可以把 Control 放看,滑鼠左鍵按著不要放,把拖曳線移到 Tab Bar Controller 之後放開,會出現 Outlets\rootController 的視窗,點選 rootController。

你可以按下 +2 看到 Tabs App Delegate 的 Outlets 內,有一個 rootController 連到 Tab Bar Controller

把 MainWindow.xib 存檔 +S 後,回到 Xcode 開啓 TabsAppDelegate.h 的檔案,把剛剛的 Tab Bar Controller(rootController) Outlet 定義進來

#import _UIKit/UIKit.h_


@interface TabsAppDelegate : NSObject {

UIWindow *window;

IBOutlet UITabBarController *rootController;

}


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

@property (nonatomic, retain) IBOutlet UITabBarController *rootController;

@end

接下來要建立兩個 UIViewController 分別給這兩個標籤項目 Tab Item,一個是基本的 UIViewController,另一個是 UINavigationController

在 Xcode 的 Classes 上面按右鍵選 Add\New File,選 Cocoa Touch Class 的 UIViewController subclass,把 Option 勾選 With XIB for user interface,一併把畫面產生出來
名稱: FirstViewController.m

在 Classes 內會看到 FirstViewController 的 .h .m .xib 三個檔案

我通常會把 .xib 放在 Resources 內

把 FirstViewController.xib 打開編輯,拖曳一個 Lable 進來,內容為【第一頁】
然後存檔,開啓 MainWindow.xib,我們要讓第一個標籤與 FirstViewController 連結。
1.點選 Tab Bar Controller 的第一個標籤,按 +4 把 Class 指定 FirstViewController
2.按 +1 把 NIB Name 指定 FirstViewController

然後就可以馬上看到 View 畫面變成這樣:

記得存檔 +S,再回到 Xcode 打開 TabsAppDelegate.m 修改如下:

#import "TabsAppDelegate.h"

@implementation TabsAppDelegate


@synthesize window;

@synthesize rootController;


- (void)applicationDidFinishLaunching:(UIApplication *)application {


// Override point for customization after application launch

[window addSubview:rootController.view];

[window makeKeyAndVisible];

}


- (void)dealloc {

[window release];

[rootController release];

[super dealloc];

}


@end


執行看看,點選標籤1與標籤2,是不是正常切換:

接下來就是重點了,要把 Navigation 設定在標籤2

回到 Xcode 加入一個 UIViewController subclass 取名 MyNavigationController,跟之前 FirstViewController 一樣,不過要把 MyNavigationController.h 的 @interface 繼承自 UIViewController 改成 UINavigationController

#import _UIKit/UIKit.h_


@interface MyNavigationController : UINavigationController {


}


@end

接著開啓 TabsAppDelegate.h 修改如下:

#import _UIKit/UIKit.h_

#import "MyNavigationController.h"


@interface TabsAppDelegate : NSObject {

UIWindow *window;

IBOutlet UITabBarController *rootController;

MyNavigationController *navigationController;

}


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

@property (nonatomic, retain) IBOutlet UITabBarController *rootController;

@property (nonatomic, retain) MyNavigationController *navigationController;


@end


記得回到 TabsAppDelegate.m 加上

@synthesize navigationController;

還有,Navigation Controller 也要有一個 View Controller,所以繼續加上一個 UIViewController 取名 SecondViewController 的 UIViewController subclass,並建立 SecondViewController.xib

開啓 SecondViewController.xib 修改 View:
存檔 +S 後,開啓 MainWindow.xib

與 FirstViewController 做法一樣,點選標籤2,把 Class 設定 MyNavigationController

接著點選 MainWindow.xib 的 Tab Bar Controller
按 +1 把 Title "第二頁" 的 Class 改成 "Navigation Controller"

再來把 MainWindow.xib 的 Tab Bar Controller 展開,點選第二頁的 View Controller

按 +4 把 Class 設為 SecondViewController


按 +1 把 NIB Name 設為 SecondViewController


接著就可以看到 View 的畫面變成這樣
執行看看吧!
第二頁是 Navigation 的 ViewController


如果底下的 Toolbar 不要顯示,可以在 MainWindow.xib 把 Tab Bar Controller 展開,點選 Navigation Controller (第二頁)
按 +1 把 Shows Toolbar 勾選取消即可


很簡單吧!多做幾次就熟悉了,你也可以任意加入其他 UIViewController

==============================

再進階把 SecondViewController 的 UIViewController 換成 UITableViewController:

回到 Xcode 在 Classes 按右鍵 Add \ New File,選 Objective-C Object,Subclass of 選 UITableViewController,取名 MyTableViewController

開啓 MyTableViewController.h 修改如下:

#import


@interface MyTableViewController : UITableViewController {

NSMutableArray *tableData;

}


@property (nonatomic, retain) NSMutableArray *tableData;


@end

開啓 MyTableViewController.m 加上:

@synthesize tableData;

- (void)viewDidLoad {

//[super viewDidLoad];

tableData = [[NSMutableArray alloc] initWithObjects:@"", @"", @"", nil];

}


在 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

// Set up the cell... 下面加上

cell.text = [tableData objectAtIndex:indexPath.row];



存檔後回到 IB 新增一個 ThirdView.xib,從 Library 拖曳一個 UITableView 放到 View 的上面排好,點選 File's Owner 在 Class Outlets 加上 Outlet: tableView,Type: UITableView

然後把關係建立如下:

開啓 MainWindow.xib 把 Tab Bar Controller 展開,點選 Second View Controller (Item)
把 Class 換成 MyTableViewController

MainWindow.xib 也跟著變了


再把 NIB Name 改成 ThirdView

執行看看,第二頁就變成有 TableView Controller 的畫面:

不錯吧!如果可以跟到這裡,那真的要好好恭喜你!

如果你無法照著我的步驟做到這裡,那真的要好好讀一讀我上一篇介紹的

基本上要讀這本 Interface Builder User Guide

*2010/10/24補充:
在新版 Xcode 的 IB,有關 Class 加入 Outlet 部份有些不同,移到了 Library 內,我在這裡補充:

點選要加入 Outlet 的 Class
在 Inspecter 的第四頁,按下這個箭頭,會開啓 Library

點選該 Class 的 Inheritance 改成 Outlets

這樣就可以繼續了

.

8 則留言:

  1. 太厲害了, 也很驚訝...小弟初學iPhone XCode, 看再多文章也比不上您這一篇, 感謝... 也給您加油!

    回覆刪除
  2. 有關 UI 一步一步的中文教學真的不多,我寫得也不是很好,請多多指教。

    回覆刪除
  3. 推一下蘋果樹的教學!
    我加了TABLE以後都顯示不出資料,照著步驟做了好多次,
    都是一樣的結果,不知道是哪個環節出了問題?
    蘋果大有這個範例的檔案可供下載嗎?
    P.S table cell的 settext在SDK3.0會waring是正常的嗎?

    回覆刪除
  4. 里歐,是不是 cellForRowAtIndexPath 內的這段?
    cell.text = [tableData objectAtIndex:indexPath.row];

    把 cell.text 改成 cell.textLabel.text 就不會 warning 了。

    cell.text 是之前 OS 2.x 的用法,後來 OS 3.x 用 cell.textLabel.text

    回覆刪除
  5. 很受用。

    請問可以簡單解釋一下為甚麼要如此建立 table view 的關係?

    例如你是怎樣知道要建立 tableView 這outlet的?

    回覆刪除
  6. Ka Yue,您可以參考在這裡提到的手冊 http://ipdevelop.blogspot.com/2009/05/view-controller-programming-guide-for.html
    就會知道 tableview 的關係

    回覆刪除
  7. 請問一下
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    SearchDataViewController *searchDataViewController = [[SearchDataViewController alloc] initWithNibName:@"SearchDataViewController" bundle:nil];
    [self.navigationController pushViewController:searchDataViewController animated:YES];
    [searchDataViewController release];
    }

    其中SearchDataViewController 是另一個繼承UITableViewController的class 可是切換時會發生錯誤。
    如果SearchDataViewController改成UITableViewController卻又可以執行~不知是否缺少什麼?

    回覆刪除
  8. 上一個問題似乎解決了
    目前發現在切換頁的時候,於viewDidLoad產生的 dataTable資料會被清掉,所以在-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 中加了
    searchDetailViewController.tableData = [NSArray arrayWithObjects:@"雞排",@"豬血糕",@"烙餅",@"糖村",nil];

    就可以了

    回覆刪除