iOS開發系列--檢視切換

KenshinCui發表於2014-08-28

概述

在iOS開發中檢視的切換是很頻繁的,獨立的檢視應用在實際開發過程中並不常見,除非你的應用足夠簡單。在iOS開發中常用的檢視切換有三種,今天我們將一一介紹:

  1. UITabBarController
  2. UINavigationController
  3. 模態視窗

UITabBarController

iOS三種檢視切換的原理各不相同:

  • UITabBarController:以平行的方式管理檢視,各個檢視之間往往關係並不大,每個加入到UITabBarController的檢視都會進行初始化即使當前不顯示在介面上,相對比較佔用記憶體。
  • UINavigationController:以棧的方式管理檢視,各個檢視的切換就是壓棧和出棧操作,出棧後的檢視會立即銷燬。
  • UIModalController:以模態視窗的形式管理檢視,當前檢視關閉前其他檢視上的內容無法操作。

UITabBarController是Apple專門為了利用頁籤切換檢視而設計的,在這個檢視控制器中有一個UITabBar控制元件,使用者通過點選tabBar進行檢視切換。我們知道在UIViewController內部有一個檢視,一旦建立了UIViewController之後預設就會顯示這個檢視,但是UITabBarController本身並不會顯示任何檢視,如果要顯示檢視則必須設定其viewControllers屬性(它預設顯示viewControllers[0])。這個屬性是一個陣列,它維護了所有UITabBarController的子檢視。為了儘可能減少檢視之間的耦合,所有的UITabBarController的子檢視的相關標題、圖示等資訊均由子檢視自己控制,UITabBarController僅僅作為一個容器存在。

UITabBarControllerLayout

假設現在有一個KCTabBarViewController(繼承於UITabBarController),它內部有一個KCWebChatViewController、一個KCContactViewController。

1.首先建立一個KCTabBarViewController繼承於UITabBarController(程式碼是預設生成的,不再貼出來)。

2.其次建立兩個子檢視,在這兩個子檢視控制器中設定對應的名稱、圖示等資訊。

KCWebChatViewController.m

//
//  KCWorldClockViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCWebChatViewController.h"

@interface KCWebChatViewController ()

@end

@implementation KCWebChatViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor=[UIColor redColor];
    
    //設定檢視控制器標題
    self.title=@"Chat";
    
    //注意通過tabBarController或者parentViewController可以得到其俯檢視控制器(也就是KCTabBarViewController)
    NSLog(@"%i",self.tabBarController==self.parentViewController);//對於當前應用二者相等
    
    //設定圖示、標題(tabBarItem是顯示在tabBar上的標籤)
    self.tabBarItem.title=@"Web Chat";//注意如果這個標題不設定預設在頁簽上顯示檢視控制器標題
    self.tabBarItem.image=[UIImage imageNamed:@"tabbar_mainframe.png"];//預設圖片
    self.tabBarItem.selectedImage=[UIImage imageNamed:@"tabbar_mainframeHL.png"];//選中圖片
    
    //圖示右上角內容
    self.tabBarItem.badgeValue=@"5";
    
}
@end

KCContactViewController.m

//
//  KCAlarmViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactViewController.h"

@interface KCContactViewController ()

@end

@implementation KCContactViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor=[UIColor yellowColor];

    self.tabBarItem.title=@"Contact";
    self.tabBarItem.image=[UIImage imageNamed:@"tabbar_contacts.png"];
    self.tabBarItem.selectedImage=[UIImage imageNamed:@"tabbar_contactsHL.png"];

}

@end

3.在應用程式啟動後設定Tab bar檢視控制器的子檢視,同時將Tab bar檢視控制器作為window的根控制器。

AppDelegate.m

//
//  AppDelegate.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "AppDelegate.h"
#import "KCTabBarViewController.h"
#import "KCWebChatViewController.h"
#import "KCContactViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate
            

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    
    KCTabBarViewController *tabBarController=[[KCTabBarViewController alloc]init];
    
    KCWebChatViewController *webChatController=[[KCWebChatViewController alloc]init];
    KCContactViewController *contactController=[[KCContactViewController alloc]init];
    tabBarController.viewControllers=@[webChatController,contactController];
    //注意預設情況下UITabBarController在載入子檢視時是懶載入的,所以這裡呼叫一次contactController,否則在第一次展示時只有第一個控制器tab圖示,contactController的tab圖示不會顯示
    for (UIViewController *controller in tabBarController.viewControllers) {
        UIViewController *view= controller.view;
    }
    
    _window.rootViewController=tabBarController;
    [_window makeKeyAndVisible];
    
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end

執行效果:

KCWebChatViewController      KCContactViewController

對於UITabBarController簡單總結如下:

  • UITabBarController會一次性初始化所有子控制器,但是預設只載入第一個控制器檢視,其他檢視控制器只初始化預設不會載入,為了能夠將其他子控制器也正常顯示在Tab bar中我們訪問了每個子檢視控制器的檢視以便呼叫其檢視載入方法(viewDidLoad);當然,既然會呼叫子檢視的初始化方法,當然也可以將檢視控制器的tabBarItem屬性設定放到init方法中設定,如此則不用再遍歷其檢視屬性了。
  • 每個檢視控制器都有一個tabBarController屬性,通過它可以訪問所在的UITabBarController,而且對於UITabBarController的直接子檢視其tabBarController等於parentViewController。
  • 每個檢視控制器都有一個tabBarItem屬性,通過它控制檢視在UITabBarController的tabBar中的顯示資訊。
  • tabBarItem的image屬性必須是png格式(建議大小32*32)並且開啟alpha通道否則無法正常顯示。

注意:使用storyboard建立UITabBarController的內容今天不再著重講解,內容比較簡單,大家可以自己試驗。

UINavigationController

程式碼方式建立導航

UINavigationController是一個導航控制器,它用來組織有層次關係的檢視,在UINavigationController中子控制器以棧的形式儲存,只有在棧頂的控制器能夠顯示在介面中,一旦一個子控制器出棧則會被銷燬。UINavigationController預設也不會顯示任何檢視(這個控制器自身的UIView不會顯示),它必須有一個根控制器rootViewController,而且這個根控制器不會像其他子控制器一樣被銷燬。

UINavigationControllerLayout

下面簡單通過幾個檢視模擬一下微信新增好友的功能,假設有一個導航控制器,它的根控制器為好友列表控制器KCFriendViewController,通過它可以導航到新增QQ聯絡人檢視KCQQContactViewController,在QQ聯絡人檢視又可以導航到公共賬號檢視KCPublicAccountViewController。

1.首先在應用代理啟動後初始化一個導航控制器並設定其根控制器為KCFriendViewController

//
//  AppDelegate.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "AppDelegate.h"
#import "KCTabBarViewController.h"
#import "KCWebChatViewController.h"
#import "KCContactViewController.h"

#import "KCFriendViewController.h"
#import "KCQQContactViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate
            

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    
    _window.backgroundColor =[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1];

    //設定全域性導航條風格和顏色
    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
    [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
    KCFriendViewController *friendController=[[KCFriendViewController alloc]init];
    UINavigationController *navigationController=[[UINavigationController alloc]initWithRootViewController:friendController];
    
    _window.rootViewController=navigationController;
    
    [_window makeKeyAndVisible];
    
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end

2.在好友列表檢視控制器中設定導航欄左右按鈕,並且設定點選右側按鈕導航到新增QQ聯絡人檢視

//
//  KCFriendViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCFriendViewController.h"
#import "KCQQContactViewController.h"

@interface KCFriendViewController ()

@end

@implementation KCFriendViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //每次出棧都會銷燬相應的子控制器
    NSLog(@"childViewControllers:%@",self.navigationController.childViewControllers);
    
    //在子檢視中可以通過navigationController屬性訪問導航控制器,
    //同時對於當前子檢視來說其父控制器就是其導航控制器
    NSLog(@"%i",self.navigationController==self.parentViewController);
    
    //在子檢視中(或者根檢視)有一個navigationItem用於訪問其導航資訊
    self.navigationItem.title=@"Friends";//或者直接設定控制器title(例如[self setTitle:@"Friends"])
    //設定導航欄左側按鈕
    self.navigationItem.leftBarButtonItem=[[UIBarButtonItem alloc]initWithTitle:@"Edit" style:UIBarButtonSystemItemAdd target:nil action:nil];
    //設定導航欄右側按鈕
    self.navigationItem.rightBarButtonItem=[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"ff_IconAdd.png"] style:UIBarButtonItemStyleDone target:self action:@selector(addFriends)];

}

-(void)addFriends{
    //通過push導航到另外一個子檢視
    KCQQContactViewController *qqContactController=[[KCQQContactViewController alloc]init];
    [self.navigationController pushViewController:qqContactController animated:YES];
}
@end

3.在QQ聯絡人檢視右側導航中新增一個導航到公共賬號的按鈕

//
//  KCQQContactViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCQQContactViewController.h"
#import "KCPublicAccountViewController.h"

@interface KCQQContactViewController ()

@end

@implementation KCQQContactViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //每次出棧都會銷燬相應的子控制器
    NSLog(@"childViewControllers:%@",self.navigationController.childViewControllers);
    
    [self setTitle:@"QQ Contact"];
    //self.title=@"QQ contact";
    //self.navigationItem.title=@"My QQ";
    
    UIBarButtonItem *back=[[UIBarButtonItem alloc]initWithTitle:@"QQ" style:UIBarButtonItemStyleDone target:nil action:nil];
    self.navigationItem.backBarButtonItem=back;
    
    self.navigationItem.rightBarButtonItem=[[UIBarButtonItem alloc]initWithTitle:@"Public Account" style:UIBarButtonItemStyleDone target:self action:@selector(gotoNextView)];
}

-(void)gotoNextView{
    KCPublicAccountViewController *publicAccountController=[[KCPublicAccountViewController alloc]init];
    [self.navigationController pushViewController:publicAccountController  animated:YES];
}
@end

4.在公共賬號檢視中在導航欄右側設定一個按鈕用於直接返回根檢視

//
//  KCPublicNumberViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCPublicAccountViewController.h"

@interface KCPublicAccountViewController ()

@end

@implementation KCPublicAccountViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //每次出棧都會銷燬相應的子控制器
    NSLog(@"childViewControllers:%@",self.navigationController.childViewControllers);
    
    self.title=@"Public Account";
    
    self.navigationItem.rightBarButtonItem=[[UIBarButtonItem alloc]initWithTitle:@"Add Friends" style:UIBarButtonItemStyleDone target:self action:@selector(gotoAddFriends)];
    
}


-(void)gotoAddFriends{
    //直接跳轉到根控制器,也可以使用- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated; 方法
    [self.navigationController popToRootViewControllerAnimated:YES];
}

@end
  • UINavigationController預設顯示一個根控制器,這個根檢視必須指定(前面我們說過UINavigationController和UITabBarController類似僅僅作為導航容器,本身並不會顯示檢視),通過根控制器導航到其他下一級子檢視。
  • 在子檢視中可以通過navigationController訪問導航控制器,同時可以通過navigationController的childViewControllers獲得當前棧中所有的子檢視(注意每一個出棧的子檢視都會被銷燬)。
  • UINavigationController導航是通過上方導航欄進行的(類似的UITabBarController是通過下方UITabBar進行導航),每個放到UINavigationController棧中的子檢視都會顯示一個導航欄,可以通過子控制器(包括根控制器)的navigationItem訪問這個導航欄,修改其左右兩邊的按鈕內容。
  • 預設情況下除了根控制器之外的其他子控制器左側都會在導航欄左側顯示返回按鈕,點選可以返回上一級檢視,同時按鈕標題預設為上一級檢視的標題,可以通過backBarButtonItem修改。下一級子檢視左側返回按鈕上的標題的顯示優先順序為: 導航欄返回按鈕backBarButtonItem的標題(注意不能直接給backBarButtonItem的標題賦值,只能重新給backBarButtonItem賦值)、導航欄navigationItem的標題,檢視控制器標題。

演示效果:

 NavigationControllerEffect

使用storyboard進行導航

鑑於很多初學者在學習UINavigationController時看到的多數是使用storyboard方式建立導航,而且storyboard中的segue很多初學者不是很瞭解,這裡簡單對storyboard方式建立導航進行介紹。

下面簡單做一個類似於iOS系統設定的導航程式,系統預設進入Settings檢視控制器,在Settings介面點選General進行General檢視,點選Sounds進入Sounds檢視,就那麼簡單。

1.首先在Main.storyboard中拖拽一個UINavigationController將應用啟動箭頭拖拽到新建的UINavigationController中將其作為預設啟動檢視,在拖拽過程中會發現UINavigationController預設會帶一個UITableViewController作為其根控制器。

2.設定UITableViewController的標題為“Settings”,同時設定UITableView為靜態表格並且包含兩行,分別在兩個UITableViewCell中放置一個UILabel命名為”General”和“Sounds”。

3.新建兩個UITableViewController,標題分別設定為“General”、“Sounds”,按住Ctrl拖拽“Settings”的第一個UITableViewCell到檢視控制器“General”,同時選擇segue為“push”,拖拽第二個UITableViewCell到檢視控制器“Sounds”,同時選擇segue為“push”。

到這裡其實我們已經可以通過Settings檢視導航到General和Sounds檢視了,但是storyboard是如何處理導航的呢?

前面我們看到導航的過程是通過一個名為“Segue”連線建立的(前面採用的是push方式),那麼這個Segue是如何工作的呢?Segue的工作方式分為以下幾個步驟:

1.建立目標檢視控制器(也就是前面的General、Sounds檢視控制器)

2.建立Segue物件

3.呼叫源檢視物件的prepareForSegue:sender:方法

4.呼叫Segue物件的perform方法將目標檢視控制器推送到螢幕

5.釋放Segue物件

要解釋上面的過程首先我們定義一個KCSettingsTableViewController控制器,它繼承於UITableViewController,然後在storyboard中設定“Settings”檢視控制器的class屬性為KCSettingsTableViewController。同時設定導航到“General”檢視控制器的segue的Identifier為“GeneralSegue”,設定導航到“Sounds”控制器的segue的Identifier為“SoundsSegue”。

然後修改KCSettingsTableViewController.m新增如下程式碼:

#pragma mark - 導航
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    //源檢視控制器
    UITableViewController *settingController=segue.sourceViewController;
    //目標檢視控制器
    UITableViewController *tableViewController=segue.destinationViewController;
    NSLog(@"sourceController:%@,destinationController:%@",settingController.navigationItem.title,tableViewController.navigationItem.title);
}

此時執行程式導航我們會發現此方法會被呼叫的同時可以列印源檢視控制器和目標檢視控制器的資訊,這一步對應上面所說的第三個步驟。

接著在”Settings”檢視控制器的導航欄左右兩側分別放一個UIBarButtonItem並新增對應事件程式碼如下:

- (IBAction)toGeneral:(id)sender {
    [self performSegueWithIdentifier:@"GeneralSegue" sender:self];
}

- (IBAction)toSounds:(id)sender {
    [self performSegueWithIdentifier:@"SoundsSegue" sender:self];
}

此時執行程式發現,使用兩個按鈕同樣可以導航到對應的檢視控制器,這一步對應上面第四個步驟,只是預設情況下是自己執行的,這裡我們通過手動呼叫來演示了這個過程。

執行效果如下:

NavigationControllerByStoryboardEffec

模態視窗

模態視窗只是檢視控制器顯示的一種方式(在iOS中並沒有專門的模態視窗類),模態視窗不依賴於控制器容器(例如前兩種檢視切換一個依賴於UITabBarController,另一個依賴於UINavigationController),通常用於顯示獨立的內容,在模態視窗顯示的時其他檢視的內容無法進行操作。

模態視窗使用起來比較容易,一般的檢視控制器只要呼叫- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion NS_AVAILABLE_IOS(5_0);方法那麼引數中的檢視控制器就會以模態視窗的形式展現,同時呼叫- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion NS_AVAILABLE_IOS(5_0);方法就會關閉模態視窗。

下面的示例中演示了一個登入操作,點選主介面左上方登入按鈕以模態視窗的形式展現登入介面,使用者點選登入介面中的登入按鈕就會返回到主介面。特別強調一點在下面的示例中導航欄是手動建立的,而不是採用UINavigationController,為了幫助大家熟悉導航欄使用同時也瞭解了UInavigationController中導航欄的本質。

1.首先建立一個登入介面,在介面中只有兩個輸入框和一個登入按鈕

//
//  KCLoginViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCLoginViewController.h"

@interface KCLoginViewController ()

@end

@implementation KCLoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addLoginForm];
}

-(void)addLoginForm{
    //使用者名稱
    UILabel *lbUserName=[[UILabel alloc]initWithFrame:CGRectMake(50, 150, 100, 30)];
    lbUserName.text=@"使用者名稱:";
    [self.view addSubview:lbUserName];
    
    UITextField *txtUserName=[[UITextField alloc]initWithFrame:CGRectMake(120, 150, 150, 30)];
    txtUserName.borderStyle=UITextBorderStyleRoundedRect;
    [self.view addSubview:txtUserName];
    
    //密碼
    UILabel *lbPassword=[[UILabel alloc]initWithFrame:CGRectMake(50, 200, 100, 30)];
    lbPassword.text=@"密碼:";
    [self.view addSubview:lbPassword];
    
    UITextField *txtPassword=[[UITextField alloc]initWithFrame:CGRectMake(120, 200, 150, 30)];
    txtPassword.secureTextEntry=YES;
    txtPassword.borderStyle=UITextBorderStyleRoundedRect;
    [self.view addSubview:txtPassword];
    
    //登入按鈕
    UIButton *btnLogin=[UIButton buttonWithType:UIButtonTypeSystem];
    btnLogin.frame=CGRectMake(120, 270, 80, 30);
    [btnLogin setTitle:@"登入" forState:UIControlStateNormal];
    [self.view addSubview:btnLogin];
    [btnLogin addTarget:self action:@selector(login) forControlEvents:UIControlEventTouchUpInside];
}

#pragma mark 登入操作
-(void)login{
    [self dismissViewControllerAnimated:YES completion:nil];
}


@end

2.定義主介面檢視控制器KCMainViewController,在左上角放一個登入按鈕用於彈出登入介面

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

#import "KCMainViewController.h"
#import "KCLoginViewController.h"

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addNavigationBar];
}


#pragma mark 新增導航欄
-(void)addNavigationBar{
    //建立一個導航欄
    UINavigationBar *navigationBar=[[UINavigationBar alloc]initWithFrame:CGRectMake(0, 0, 320, 44+20)];
    //navigationBar.tintColor=[UIColor whiteColor];
    [self.view addSubview:navigationBar];
    //建立導航控制元件內容
    UINavigationItem *navigationItem=[[UINavigationItem alloc]initWithTitle:@"Web Chat"];
    
    //左側新增登入按鈕
    UIBarButtonItem *loginButton=[[UIBarButtonItem alloc]initWithTitle:@"登入" style:UIBarButtonItemStyleDone target:self action:@selector(login)];
    
    navigationItem.leftBarButtonItem=loginButton;
    
    //新增內容到導航欄
    [navigationBar pushNavigationItem:navigationItem animated:NO];
}

#pragma mark 登入操作
-(void)login{
    KCLoginViewController *loginController=[[KCLoginViewController alloc]init];
    //呼叫此方法顯示模態視窗
    [self presentViewController:loginController animated:YES completion:nil];
}
@end

引數傳遞

假設使用者名稱輸入“kenshincui”,密碼輸入“123”就認為登入成功,否則登入失敗。同時登入成功之後在主檢視控制器中顯示使用者名稱並且登入按鈕變成“登出”。要實現這個功能主要的問題就是如何把登入後的使用者名稱資訊傳遞到主介面?由此引出一個問題:多檢視引數傳遞。

在iOS開發中常用的引數傳遞有以下幾種方法:

  1. 採用代理模式
  2. 採用iOS訊息機制
  3. 通過NSDefault儲存(或者檔案、資料庫儲存等)
  4. 通過AppDelegate定義全域性變數(或者使用UIApplication、定義一個單例類等)
  5. 通過控制器屬性傳遞

今天我們主要採用第一種方式進行資料傳遞,這在iOS開發中也是最常見的一種多檢視傳參方式。使用代理方式傳遞引數的步驟如下:

1.定義協議,協議中定義好傳參時所需要的方法

2.目標檢視控制器定義一個代理物件

3.源檢視控制器實現協議並在初始化目標控制器時指定目標控制器的代理為其自身

4.需要傳參的時候在目標視窗呼叫代理的協議方法

具體程式碼如下:

KCMainViewController.h

//
//  KCMainViewController.h
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <UIKit/UIKit.h>


#pragma mark 定義一個協議用於引數傳遞
@protocol KCMainDelegate
-(void)showUserInfoWithUserName:(NSString *)userName;
@end

@interface KCMainViewController : UIViewController

@end

KCMainViewController.m

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

#import "KCMainViewController.h"
#import "KCLoginViewController.h"



@interface KCMainViewController ()<KCMainDelegate,UIActionSheetDelegate>{
    UILabel *_loginInfo;
    UIBarButtonItem *_loginButton;
    BOOL _isLogon;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addNavigationBar];
    
    [self addLoginInfo];
}

#pragma mark 新增資訊顯示
-(void)addLoginInfo{
    _loginInfo =[[UILabel alloc]initWithFrame:CGRectMake(0, 100,320 ,30)];
    _loginInfo.textAlignment=NSTextAlignmentCenter;
    [self.view addSubview:_loginInfo];
}

#pragma mark 新增導航欄
-(void)addNavigationBar{
    //建立一個導航欄
    UINavigationBar *navigationBar=[[UINavigationBar alloc]initWithFrame:CGRectMake(0, 0, 320, 44+20)];
    //navigationBar.tintColor=[UIColor whiteColor];
    [self.view addSubview:navigationBar];
    //建立導航控制元件內容
    UINavigationItem *navigationItem=[[UINavigationItem alloc]initWithTitle:@"Web Chat"];
    
    //左側新增登入按鈕
    _loginButton=[[UIBarButtonItem alloc]initWithTitle:@"登入" style:UIBarButtonItemStyleDone target:self action:@selector(login)];
    
    navigationItem.leftBarButtonItem=_loginButton;
    
    //新增內容到導航欄
    [navigationBar pushNavigationItem:navigationItem animated:NO];
}

#pragma mark 登入操作
-(void)login{
    if (!_isLogon) {
        KCLoginViewController *loginController=[[KCLoginViewController alloc]init];
        loginController.delegate=self;//設定代理
        //呼叫此方法顯示模態視窗
        [self presentViewController:loginController animated:YES completion:nil];
    }else{
        //如果登入之後則處理登出的情況
        //注意當前檢視控制器必須實現UIActionSheet代理才能進行操作
        UIActionSheet *actionSheet=[[UIActionSheet alloc]initWithTitle:@"系統資訊" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"登出" otherButtonTitles: nil];
        [actionSheet showInView:self.view];
    }
}

#pragma mark 實現代理方法
-(void)showUserInfoWithUserName:(NSString *)userName{
    _isLogon=YES;
    //顯示登入使用者的資訊
    _loginInfo.text=[NSString stringWithFormat:@"Hello,%@!",userName];
    //登入按鈕內容改為“登出”
    _loginButton.title=@"登出";
}

#pragma mark 實現登出方法
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
    if (buttonIndex==0) {//登出按鈕
        _isLogon=NO;
        _loginButton.title=@"登入";
        _loginInfo.text=@"";
    }
}
@end

KCLoginViewController.h

//
//  KCLoginViewController.h
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <UIKit/UIKit.h>
@protocol KCMainDelegate;

@interface KCLoginViewController : UIViewController

#pragma mark 定義代理
@property (nonatomic,strong) id<KCMainDelegate> delegate;

@end

KCLoginViewController.m

//
//  KCLoginViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCLoginViewController.h"
#import "KCMainViewController.h"

@interface KCLoginViewController (){
    UITextField *_txtUserName;
    UITextField *_txtPassword;
}

@end

@implementation KCLoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addLoginForm];
}

-(void)addLoginForm{
    //使用者名稱
    UILabel *lbUserName=[[UILabel alloc]initWithFrame:CGRectMake(50, 150, 100, 30)];
    lbUserName.text=@"使用者名稱:";
    [self.view addSubview:lbUserName];
    
    _txtUserName=[[UITextField alloc]initWithFrame:CGRectMake(120, 150, 150, 30)];
    _txtUserName.borderStyle=UITextBorderStyleRoundedRect;
    [self.view addSubview:_txtUserName];
    
    //密碼
    UILabel *lbPassword=[[UILabel alloc]initWithFrame:CGRectMake(50, 200, 100, 30)];
    lbPassword.text=@"密碼:";
    [self.view addSubview:lbPassword];
    
    _txtPassword=[[UITextField alloc]initWithFrame:CGRectMake(120, 200, 150, 30)];
    _txtPassword.secureTextEntry=YES;
    _txtPassword.borderStyle=UITextBorderStyleRoundedRect;
    [self.view addSubview:_txtPassword];
    
    //登入按鈕
    UIButton *btnLogin=[UIButton buttonWithType:UIButtonTypeSystem];
    btnLogin.frame=CGRectMake(70, 270, 80, 30);
    [btnLogin setTitle:@"登入" forState:UIControlStateNormal];
    [self.view addSubview:btnLogin];
    [btnLogin addTarget:self action:@selector(login) forControlEvents:UIControlEventTouchUpInside];
    
    //取消登入按鈕
    UIButton *btnCancel=[UIButton buttonWithType:UIButtonTypeSystem];
    btnCancel.frame=CGRectMake(170, 270, 80, 30);
    [btnCancel setTitle:@"取消" forState:UIControlStateNormal];
    [self.view addSubview:btnCancel];
    [btnCancel addTarget:self action:@selector(cancel) forControlEvents:UIControlEventTouchUpInside];
}

#pragma mark 登入操作
-(void)login{
    if ([_txtUserName.text isEqualToString:@"kenshincui"] && [_txtPassword.text isEqualToString:@"123"] ) {
        //呼叫代理方法傳參
        [self.delegate showUserInfoWithUserName:_txtUserName.text];
        
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    else{
        //登入失敗彈出提示資訊
        UIAlertView *alertView=[[UIAlertView alloc]initWithTitle:@"系統資訊" message:@"使用者名稱或密碼錯誤,請重新輸入!" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil];
        [alertView show];
    }
    
}

#pragma mark 點選取消
-(void)cancel{
    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

在上面的程式碼中,點選登入可以跳轉到登入介面,如果使用者名稱、密碼輸入正確可以回傳引數到主介面中(不正確則給出提示),同時修改主介面按鈕顯示內容。如果已經登入則點選登出會彈出提示,點選確定登出則會登出登入資訊。在程式碼中我們還用到了UIActionSheet和UIAlert,這兩個控制元件其實也是模態視窗,只是沒有鋪滿全屏,大家以後的開發中會經常用到。

假設登入之後在主檢視控制器右上角點選“我”可以彈出當前使用者資訊如何實現呢?這個時候我們需要從主檢視控制器傳遞引數到子檢視控制器,和前面的傳參剛好相反,這個時候我們通常使用上面提到的第五個方法,設定目標檢視控制器的屬性。

1.首先修改一下主檢視控制器

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

#import "KCMainViewController.h"
#import "KCLoginViewController.h"
#import "KCMeViewController.h"



@interface KCMainViewController ()<KCMainDelegate,UIActionSheetDelegate>{
    UILabel *_loginInfo;
    UIBarButtonItem *_loginButton;
    UIBarButtonItem *_meButton;
    BOOL _isLogon;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addNavigationBar];
    
    [self addLoginInfo];
}

#pragma mark 新增資訊顯示
-(void)addLoginInfo{
    _loginInfo =[[UILabel alloc]initWithFrame:CGRectMake(0, 100,320 ,30)];
    _loginInfo.textAlignment=NSTextAlignmentCenter;
    [self.view addSubview:_loginInfo];
}

#pragma mark 新增導航欄
-(void)addNavigationBar{
    //建立一個導航欄
    UINavigationBar *navigationBar=[[UINavigationBar alloc]initWithFrame:CGRectMake(0, 0, 320, 44+20)];
    //navigationBar.tintColor=[UIColor whiteColor];
    [self.view addSubview:navigationBar];
    //建立導航控制元件內容
    UINavigationItem *navigationItem=[[UINavigationItem alloc]initWithTitle:@"Web Chat"];
    
    //左側新增登入按鈕
    _loginButton=[[UIBarButtonItem alloc]initWithTitle:@"登入" style:UIBarButtonItemStyleDone target:self action:@selector(login)];
    
    navigationItem.leftBarButtonItem=_loginButton;
    
    //左側新增導航
    _meButton=[[UIBarButtonItem alloc]initWithTitle:@"我" style:UIBarButtonItemStyleDone target:self action:@selector(showInfo)];
    _meButton.enabled=NO;
    navigationItem.rightBarButtonItem=_meButton;
    
    //新增內容到導航欄
    [navigationBar pushNavigationItem:navigationItem animated:NO];
}

#pragma mark 登入操作
-(void)login{
    if (!_isLogon) {
        KCLoginViewController *loginController=[[KCLoginViewController alloc]init];
        loginController.delegate=self;//設定代理
        //呼叫此方法顯示模態視窗
        [self presentViewController:loginController animated:YES completion:nil];
    }else{
        //如果登入之後則處理登出的情況
        //注意必須實現對應代理
        UIActionSheet *actionSheet=[[UIActionSheet alloc]initWithTitle:@"系統資訊" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"登出" otherButtonTitles: nil];
        [actionSheet showInView:self.view];
    }
}

#pragma mark 點選檢視我的資訊
-(void)showInfo{
    if (_isLogon) {
        KCMeViewController *meController=[[KCMeViewController alloc]init];
        meController.userInfo=_loginInfo.text;
        [self presentViewController:meController animated:YES completion:nil];
    }
}

#pragma mark 實現代理方法
-(void)showUserInfoWithUserName:(NSString *)userName{
    _isLogon=YES;
    //顯示登入使用者的資訊
    _loginInfo.text=[NSString stringWithFormat:@"Hello,%@!",userName];
    //登入按鈕內容改為“登出”
    _loginButton.title=@"登出";
    _meButton.enabled=YES;
}

#pragma mark 實現登出方法
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
    if (buttonIndex==0) {//登出按鈕
        _isLogon=NO;
        _loginButton.title=@"登入";
        _loginInfo.text=@"";
        _meButton.enabled=NO;
    }
}
@end

2.新增展示使用者資訊的控制器檢視

KCMeViewController.h

//
//  KCMeViewController.h
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface KCMeViewController : UIViewController

#pragma mark 需要傳遞的屬性引數(很多時候它是一個資料模型)
@property (nonatomic,copy) NSString *userInfo;

@end

KCMeViewController.m

//
//  KCMeViewController.m
//  ViewTransition
//
//  Created by Kenshin Cui on 14-3-15.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMeViewController.h"

@interface KCMeViewController (){
    UILabel *_lbUserInfo;
}

@end

@implementation KCMeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //資訊顯示標籤
    _lbUserInfo =[[UILabel alloc]initWithFrame:CGRectMake(0, 100,320 ,30)];
    _lbUserInfo.textAlignment=NSTextAlignmentCenter;
    _lbUserInfo.textColor=[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1];
    [self.view addSubview:_lbUserInfo];
    
    //關閉按鈕
    UIButton *btnClose=[UIButton buttonWithType:UIButtonTypeSystem];
    btnClose.frame=CGRectMake(110, 200, 100, 30);
    [btnClose setTitle:@"關閉" forState:UIControlStateNormal];
    [btnClose addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnClose];
    
    //設定傳值資訊
    _lbUserInfo.text=_userInfo;
}

#pragma mark 關閉
-(void)close{
    [self dismissViewControllerAnimated:YES completion:nil];
}
@end

前面程式碼中除了演示了模態視窗的使用還演示了兩種多檢視引數傳遞方法,其他方法日後我們再做介紹。最後完整展現一下整個示例程式:

ModalViewControllerEffect

相關文章