- iPhone UIKit詳解
- 王志剛 王中元 朱蕾編著
- 3720字
- 2019-01-01 07:04:26
3.2 畫面跳轉
3.2.1 使用UITabBarController實現并列畫面跳轉
前一節介紹了由UIViewController實現的畫面切換。實際上并非真的實現了兩個畫面間的跳轉,而是同時啟動兩個畫面,控制其中哪一個畫面顯示在前臺,哪一個畫面顯示在后臺而已。這種畫面跳轉方式有一個很大的缺點,即當畫面數量增加時,畫面跳轉的實現代碼將越來越復雜,而且各個畫面間不可避免地有相互依賴關系。
實際上,在UIKit中提供了專用的管理畫面跳轉的類,這就是UITabBarController類以及UINavigationController類。本節首先介紹使用UITabBarController類如何實現畫面間的跳轉功能。
下面我們以上一節實例代碼為基礎,將其改造成使用UITabBarController類來實現畫面間的跳轉。首先將HelloWorldAppDelegate改名為MultiViewAppDelegate,并進行如下修改(代碼中粗體字部分)。
[MultiViewAppDelegate.h] #import <UIKit/UIKit.h> @interface MultiViewAppDelegate :NSObject <UIApplicationDelegate> { UIWindow *window; UIViewController *rootController; } @property(nonatomic,retain)UIWindow *window; @end [MultiViewAppDelegate.m] #import "MultiViewAppDelegate.h" #import "ViewController1.h" #import "ViewController2.h" @implementation MultiViewAppDelegate @synthesize window; #pragma mark - #pragma mark Application lifecycle -(BOOL)application:(UIApplication *)application didFinishLaunchin gWithOptions:(NSDictionary *)launchOptions { // 初始化window實例變量 CGRect frame = [[UIScreen mainScreen] bounds]; self.window = [[UIWindow alloc] initWithFrame:frame]; // 創建母體Controller實例 rootController = [[UITabBarController alloc] init]; // 創建畫面1與畫面2的Controller實例 ViewController1 *tab1 = [[[ViewController1 alloc] init] autorelease]; ViewController2 *tab2 = [[[ViewController2 alloc] init] autorelease]; // 將畫面1、畫面2的Controller實例以數組的形式追加到母體Controller中 NSArray *tabs = [NSArray arrayWithObjects:tab1,tab2,nil]; [(UITabBarController)rootController setViewControllers:tabs animated:NO]; // 將母體Controller的view追加到Window中 [self.window addSubView:rootController.view]; [self.window makeKeyAndVisible]; return YES; } -(void)dealloc { [rootController release]; [window release]; [super dealloc]; } @end
要修改的地方并不多。首先刪除前例中單獨創建兩個畫面的實例變量ViewControllerl以及ViewController2,取而代之的是UITabBarController類型的實例變量rootController。上例中將兩個畫面的view直接追加到Window中,本例中只需要將UITabBarController類型實例變量的view追加到Window中。而將兩個畫面的UIViewController對象以數組的形式追加到UITabBarController類型的實例變量中,此時調用了setViewControllers:animated:方法,此方法的第一個參數即為UIViewController對象數組。
接著,我們開始修改兩個畫面的實例代碼。其實施方法如下。
- 按鈕去掉,刪除與按鈕相關的代碼。
- 重寫(或稱覆蓋)init方法,其中追加與標簽相關的代碼。
兩個畫面中的init方法的代碼如下。
[ViewController1.m] -(id)init { if((self = [super init])){ // 設置tabBar的相關屬性 self.title = @"Hello"; UIImage*icon = [UIImage imageNamed:@"ball1.png"]; self.tabBarItem = [[[UITabBarItem alloc] initWithTitle:@"Hello" image:icon tag:0] autorelease]; } return self; } [ViewController2.m] -(id)init { if((self = [super init])){ // 設置tabBar的相關屬性 self.title = @"您好"; UIImage*icon = [UIImage imageNamed:@"ball2.png"]; self.tabBarItem = [[[UITabBarItem alloc] initWithTitle:@"您好" image:icon tag:0] autorelease]; } return self; }
重寫的init方法中也沒有追加多少內容。設置title屬性后,導入圖標用的圖片,并將其設置到UIViewController的 tabBarItem屬性中。調用initWithTitle:image:tag:方法進行UITabBarItem類的初始化。第一個參數為標簽條中顯示的標題;第二個參數為指定顯示的圖標圖片;第三個參數為標簽的序號,此序號通常用于程序內檢索。
執行這個經過改造的工程后,將顯示如圖3-3所示的結果。

圖3-3 標簽實現的畫面跳轉
3.2.2 使用UINavigationController實現多層畫面跳轉
iPhone4手機的自帶應用程序中,既有使用UITabBarController來進行畫面切換控制的,也有使用UINavigationController來實現多畫面間的跳轉的。例如iPod音樂播放界面就采用了UITabBarController來進行畫面切換控制,而iPhone手機設置程序則采用了UINavigationController來實現多層次畫面間的跳轉,圖3-4、圖3-5是這兩個程序部分畫面的跳轉示意圖。

圖3-4 iPod音樂播放程序畫面跳轉示意圖
與UITabBarController實現畫面并行切換形式不同,UINavigationController是實現畫面多層次跳轉,也是其最大的特征。如圖3-5所示,畫面1-1可以跳轉至其下一層的畫面1-1-1以及畫面1-1-2中。另外UINavigationController可以自動地記憶跳轉所經過的路徑,按照這些記錄的路徑信息,可以依次返回到上層畫面中(即支持返回按鈕)。

圖3-5 iPhone設置程序部分畫面跳轉示意圖
下面我們將上一節采用UITabBarController實現的畫面切換程序,再一次改造成采用UINavigationController來實現畫面的跳轉程序。其中兩個下一層的畫面保持不變,在這之前我們還將追加一個主畫面,主畫面非常簡單,只有一個iPhone表格視圖組成,兩個下層畫面的名稱依次顯示在表格中,當單擊任何一個名稱時將會跳轉到對應的下層畫面中,整個應用程序的跳轉示意圖如圖3-6所示。

圖3-6 改造目標程序的跳轉示意圖
要實現上述程序,首先要進行如下兩處修改。
● 在HelloWorldAppDelegate.m中將基準ViewController由UITableViewController替換為UINavigationController。
● 新追加主畫面的TopMenuController類。
需要注意一點,這里我們對畫面1以及畫面2的實現代碼其實是沒有進行任何修改的。只是留下了些原實例中追加的關于UITabBarController的注釋,請務必忽略。
下面我們看看HelloWorldAppDelegate.m的修改代碼,見代碼中的黑體字部分。
[HelloWorldAppDelegate.m] #import "HelloWorldAppDelegate.h" #import "TopMenuController.h" @implementation HelloWorldAppDelegate @synthesize window = window_; -(void)applicationDidFinishLaunching:(UIApplication *)application { // 初始化Window實例變量 CGRect bounds = [[UIScreen mainScreen] bounds]; window_ = [[UIWindow alloc] initWithFrame:bounds]; // 創建基準的Controller對象 TopMenuController*topMenu = [[[TopMenuController alloc] init] autorelease]; rootController_ = [[UINavigationController alloc] initWithRootVi ewController:topMenu]; // 將主畫面的view追加到Window中 [window_ addSubview:rootController_.view]; [window_ makeKeyAndVisible]; } -(void)dealloc { [rootController_ release]; [window_ release]; [super dealloc]; } @end
大家可以看到,其實只修改了其中三行代碼。首先需要導入 TopMenuController類,并創建其實例。接著初始化UINavigationController,需要向initWithRootViewController:方法中傳入根畫面的Controller,這里將創建好的TopMenuController實例傳入。然后將UINavigationController的view屬性追加到UIWindow中(使用addSubView:方法)。
下面是新追加的主菜單畫面TopMenuController的代碼。這里將TopMenuController以UITableViewController子類形式來創建。
[TopMenuController.h] #import <UIKit/UIKit.h> @interface TopMenuController :UITableViewController { @private NSMutableArray* items_; } @end [TopMenuController.m] #import "TopMenuController.h" @implementation TopMenuController -(void)dealloc { [items_ release]; [super dealloc]; } -(id)init { if((self = [super initWithStyle:UITableViewStylePlain])){ self.title = @"主菜單"; //-------------------------------<1> // 創建顯示用數組 items_ = [[NSMutableArray alloc] initWithObjects: @"ViewController1", @"ViewController2", nil ]; } return self; } #pragma mark ----- UITableViewDataSource Methods ----- -(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { return [items_ count]; } -(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath { // 檢查單元是否已經創建 UITableViewCell* cell = [tableView dequeueReusableCellWithIdenti fier:@"simple-cell"]; if(!cell){ // 沒有創建的單元新創建 cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"simple-cell"] autorelease]; } // 設置單元中顯示的文本字符串 cell.textLabel.text = [items_ objectAtIndex:indexPath.row]; //------<2> return cell; } #pragma mark ----- UITableViewDelegate Methods ---------<3> -(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath { Class class = NSClassFromString([items_ objectAtIndex:indexPath.row]); id viewController = [[[class alloc] init] autorelease]; if(viewController){[self.navigationController pushViewController:viewController animated:YES]; } } @end
與表相關的解說將放在第7章中,代碼中有不理解的地方,請參考第7章的相關介紹。首先看看init方法中的內容。在titile屬性中設置畫面的標題(<1>)。使用UINavigationController時,此處設置的標題將在畫面的最上方中心位置顯示(見圖3-7)。接著還創建了NSArray數組,NSArray數組中的元素將顯示在表中。具體處理在tableView:cellForRowAtIndexPath:方法中,將NSArray中的元素設置到表的單元中。

圖3-7 UINavigationController的標題
其次,我們再確認一下tableView:didSelectRowAtIndexPath:方法的處理內容。此方法將在表單元被觸摸(單擊)時調用,參數indexPath中保存了具體被觸摸(單擊)的行信息。
本例中,表單元中直接放置了跳轉對象畫面的類名,tableView:didSelectRow AtIndexPath:方法中首先創建被觸摸行類的實例。然后跳轉到對應畫面中。調用UINavigationController的pushViewController:animated:方法實現畫面跳轉;向此方法的第一個參數中傳入畫面(UIViewController)的實例后,然后就會自動跳轉到對應層次的畫面中。另外如果將animated參數設置為YES,則下一畫面將以動畫的形式顯示出來。此時UINavigationController實例可以通過UIViewController的navigationController屬性獲取。只要是UINavigationController管理下的UIViewController,隨時都可以通過其navigationController屬性獲取UINavigationController實例本身。
這樣就算完成了整個層次的畫面跳轉應用程序。跳轉到下一層畫面后,將自動顯示如圖3-8所示的返回按鈕。

圖3-8 UINavigationController的返回按鈕
3.2.3 跳轉到任意畫面
上一小節中通過pushViewController:animated:方法能實現畫面的跳轉,而且能在導航條上自動追加返回上一畫面的返回按鈕。這種“返回到前一畫面”的功能正確的表述應該為“返回到上一級”畫面,調用popViewControllerAnimation:方法也能實現同樣的功能。其他的如UINavigationController類還提供了直接返回到主畫面的popToRootViewControllerAnimation:方法,以及返回到任意一級畫面的popToViewController:animated:方法,以下是這三種方法的調用代碼。
// 返回到上一級畫面 [self.navigationController popViewControllerAnimation:Yes]; // 返回根畫面 [self.navigationController popToRootViewControllerAnimation:Yes]; // 返回任意指定畫面 [self.navigationController popToViewController:viewController animated:Yes];
另外,從iPhone OS 3.0以后,可以通過調用setViewController:animated:方法將畫面的跳轉歷史路徑(堆棧)完全替換。替換歷史路徑的示意圖如圖3-9所示。

圖3-9 替換歷史路徑
實現圖3-9所示的替換歷史路徑的代碼如下。
// 將跳轉歷史路徑替換為畫面1> 畫面1-3> 畫面1-3-1 id scene1 = [[[Scene alloc] init] autorelease]; // 創建畫面1的實例 id scene13 = [[[Scene3 alloc] init] autorelease]; // 創建畫面1-3的實例 id scene131 = [[[Scene31 alloc] init] autorelease]; // 創建畫面1-3-1的實例 NSArray *history = [NSArray arrayWithObjects:scene1,scene13,scene13 1,nil]; [self.navigationController setViewControllers:history animated:Yes];
popToViewController:animated:方法的第一個參數中必須傳入UIViewController的實例,不是新創建的實例,而是實際跳轉過程中原畫面的實例。此時,可以通過UINavigationController的viewControllers屬性來參照。viewControllers屬性中保存的正是NSArray形式的跳轉畫面實例集合。
調用setViewControllers:animated方法進行跳轉畫面堆棧替換時,也可以viewControllers屬性中保存的實例集合為基礎,進行部分UIViewController實例的替換。
3.2.4 模態(modal)畫面的顯示方法
PC桌面軟件中經常可以看到如“文件讀取對話框”等模態對話框的畫面類型。這些畫面就顯示在主畫面的上方,當對話框中的操作結束,關閉對話框畫面后將顯示原來的畫面,屬于一種臨時畫面。iPhone應用程序中也能實現這種模態畫面,例如iPhone通信錄管理程序中,追加新的通信錄時也使用了這種模態畫面。
模態畫面沒有什么特別的地方,與其他畫面一樣也是由UIViewController的子類實現的畫面,只是調用的方式不同而已。以下是模態畫面顯示的調用方式以及顯示后關閉畫面的實例代碼。
// ModalDialog為UIViewController的子類 id dialog = [[[ModalDialog alloc] init] autorelease]; [self presentModalViewController:dialog animated:YES]; // 關閉模態UIViewController [self dismissModalViewControllerAnimationed:YES];
如上述代碼所示,將UIViewController子類的實例作為presentModalViewController:animated:方法的第一個參數進行調用后,就能實現以模態方式顯示畫面。關閉時調用dismissModalViewControllerAnimationed:方法。模態畫面調用后的示意圖如圖3-10所示。

圖3-10 模態畫面顯示示意圖
從iPhone OS 3.0開始,追加了設置模態畫面顯示/隱藏時動畫效果的modalTranstionStyle屬性,可設置三種不同的值,分別如下。
● UIModalTransitionStyleCoverVertical:畫面從下向上徐徐彈出,關閉時向下隱藏(默認方式)。
● UIModalTransitionStyleFlipHorizontal:從前一個畫面的后方,以水平旋轉的方式顯示后一畫面。
● UIModalTransitionStyleCrossDissolve:前一畫面逐漸消失的同時,后一畫面逐漸顯示。
以下是圖3-10所示模態畫面的代碼,僅供參考。大家可以看到,其與普通的UIViewController子類沒有任何區別。
// 以模態形式顯示的畫面 // 內容與普通畫面一樣 @interface ModalDialog :UIViewController @end @implementation ModalDialog -(void)viewDidLoad { [super viewDidLoad]; // 追加1個標簽 UILabel* label = [[[UILabel alloc] initWithFrame:self.view.bounds] autorelease]; label.backgroundColor = [UIColor blackColor]; label.textColor = [UIColor whiteColor]; label.textAlignment = UITextAlignmentCenter; label.text = @"您好。我是模態畫面。"; [self.view addSubview:label]; // 追加關閉按鈕 UIButton* goodbyeButton = [UIButton buttonWithType:UIButtonTypeR oundedRect]; [goodbyeButton setTitle:@"Good-bye" forState:UIControlStateNormal]; [goodbyeButton sizeToFit]; CGPoint newPoint = self.view.center; newPoint.y += 80; goodbyeButton.center = newPoint; [goodbyeButton addTarget:self action:@selector(goodbyeDidPush) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:goodbyeButton]; } -(void)goodbyeDidPush { // 關閉模態對話框 [self dismissModalViewControllerAnimated:YES]; } @end