iOS App開發 – UIViewController 的七種切換頁面技巧
在開發 iOS App 時,UIViewController 之間的切換是很基本且必要的動作。
除非你只是要開發一個超精簡的單頁的控制程式,不然一定會寫到這個動作。
本篇可參考範例程式:https://github.com/iOTEC/ViewControllerSwitchExample
基礎篇
有一件事要先對新手說明:基礎並不是表示不好用或是比較遜,反而,若能用基礎的方式完成就一定要用這裡的方式才是正確的,老手是把難的程式寫得簡單,新手反而才會把簡單的程式寫得很複雜,然後還常產生一堆 Bug。
只有當簡單的方式無法達成任務的時候,才需要找尋更進階的作法。
Method 1. 用 Storyboard 的 Segue 切換方式
這是最簡單的方。參考專案裡的Storyboard裡的 Root 裡的 「Method 1. to A」按鈕,先點選,然後壓住 Ctrl 的情況下,將按鈕拖至 A 再放開,就會出現這個選單:
選擇「Show」,這樣就大功告成了!一行程式都不用寫,簡單吧!? 這個技巧文字上比較不容易理解,不過真正試一次就會懂了。
而且,如果需要在切換頁之前先作點什麼處理的話,可以在程式裡加上prepareForSegue函式來進行
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
}
Method 2. 用 performSegueWithIdentifier 來執行預先定義好的 Segue
1. 在 Storyboard 裡先點選 Root ,然後拖至 B ,產生一個 Segue
2. 點選該 Segue,我們給它個名字叫 RootToB好了
3. 點 Assist Editor 切換為 Storyboard 跟 Code 並列模式
4. 與 Method 1 同樣的技巧,點按鈕,然後拖至右方 code 處放開,在下方Name欄填入 method2Clicked
5. 就會生成按鈕的連接函式:
左方有個實心的點表示這個 method 與 Storyboard 裡的元件有作連結
6. 接著加上 code :
[self performSegueWithIdentifier:@"RootToB" sender:nil];
這樣就大功告成了。
這個作法適合在按了按鈕之後,還有要作許多前置作業,以及其它並不是由按鈕觸發的情況。彈性比 Method 1來得高,不過當然要作的事會多一點。
方式3. 用 Storyboard 的 Storyboard ID 來切換
參考專案的「Method 3. To D」
1. 先為 D 的Storyboard ID取個名字,叫「ViewControllerDIdentifier」
2. 在ViewControllerRoot.m一開頭#import “ViewControllerD.h”
3. 參考Method 2 裡介紹的方法,為「Method 3. To D」按鈕產生一個連結的程式碼叫「method3Clicked」
然後填上以下的 Code 即大功告成
- (IBAction)method3Clicked:(id)sender {
UIStoryboard *storyboard=[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
ViewControllerD *controllerD = [storyboard instantiateViewControllerWithIdentifier:@"ViewControllerDIdentifier"];
[self.navigationController pushViewController:controllerD animated:YES];
}
Swift version
@IBAction func method3Clicked(_ sender: Any) {
let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
if let vc = mainStoryboard.instantiateViewController(withIdentifier: "ViewControllerDIdentifier") as? ViewControllerD
{
navigationController?.pushViewController(vc, animated: true)
}
}
這個方式並不需要借助於 Segue 的幫助,而是使用 Storyboard ID 來任意取用所需要的頁面,彈性很高,也是很常用的方法。
Method 4. 回前頁的三種常作法
1. 什麼都沒寫,點左上的「<」按鈕自動會回上頁
這是預設的功能,很方便。
不過並不像 Segue 一樣,可以在回前頁之前作點什麼事,是比較不方便的地方。
2. 撰寫程式碼來回前頁
相對的,若需要前頁前作許多前置處理的話,可以改用:
[self.navigationController popViewControllerAnimated:YES];
3. 撰寫程式碼來回到首頁
假設你的程式一開始由 Root 頁,Push 了 A來到 A頁,然後再 Push 了 C 來到C頁,這時候若想要按一個按鈕即回到 Root 頁而不需要先回到 A 頁,可以用:
[self.navigationController popToRootViewControllerAnimated:YES];
進階篇
如果前面的4種都不適用的話,就要使用一些進階的技巧,在進行之前,要先對 NavigationController 有進一步的了解:
NavigationController 是 iOS 裡一個 Container 元件,真正呈現在使用者之前的是 UIViewController 或是 UITableViewController,而UINavigationController 則是負責作頁面切換等等的工作。
其運作的原理之一是 UINavigationController. viewControllers 陣列,這是我們這裡要借力的地方。viewControllers 陣列裡面存放了 ViewController Stack(堆疊),在 Push 時將目前的放進 Stack 裡,而在 Pop 時則將最上面的 ViewController 放出來。
以範例程式來說,其內的頁面切換結構如下圖:
其 self.navigationController.viewControllers 陣列在每個頁面裡會是這樣:

Method 5. 直接回到前面的某一頁
如果很確定 viewControllers 的堆疊狀況,可以使用下面這段程式碼來進行:
NSArray *array = [self.navigationController viewControllers];
[self.navigationController popToViewController:[array objectAtIndex:堆疊編號] animated:YES];
不過這個程式碼的缺點是維護上不容易,若後面有需求變更了堆疊的順序,那麼就容易亂掉了。使用上要多小心。
Method 6. 直接回到前面的特定個 UIViewController
如果知道要回去的某頁的 Class 的話,可以這樣作:
for (UIViewController *controller in self.navigationController.viewControllers) { if ([controller isKindOfClass:[你的UIViewController名 class]]){ [self.navigationController popToViewController:controller animated:YES]; break; } }
Swift version
if let controllers = navigationController?.viewControllers {
for vc in controllers {
if vc is <你的UIViewController名>{
navigationController?.popToViewController(vc, animated: true)
}
}
}
這個作法彈性就比 Method 5好一些,不會因為後來插了某一頁進來而亂掉。不過也不是不會有出錯的可能,假如說:用Method 3 的作法先 Push A 再 Push B 再 Push A 再 Push B, 這時堆疊會變成:
若使用這段程式碼,它會跑到 Index=1 的 A 而不是 Index=3 的A。不過一般來說應該很少會遇到這種情況。
Method 7. 切換到並沒有 Push 過的 UIViewController,但是又不要成為目前頁面的下一頁
範例程式裡的切頁結構來說明:
如果我打算由 C 切到 D ,那麼前面說明的 1~6 種作法立刻都不適用了。
比較接近的作法是 Method 3, 不過某使用 Method 3. 其實會創造出另一個路徑 而已,雖然也是 D沒錯,是按 「<」時會回到C而不是回到B。
要解決這個問題,可以對 Stack 直接進行操作:
NSMutableArray *controllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
UIViewController *rootController = controllers[0]; // 保留 rootController
[controllers removeAllObjects]; // 移除全部
[controllers addObject:rootController]; // 先將 rootController 放到 index 0
ViewControllerB *vcB = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerBIdentifier"];
[controllers addObject:vcB]; // 建立並放入 B 在 index 1
ViewControllerD *vcD = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerDIdentifier"];
[controllers addObject:vcD]; // 建立並放入 D 在 index 2
[[self navigationController] setViewControllers:controllers animated:YES]; // 這行十分重要,要請 navigationController 重新用新的 stack 執行
Swift version
if let controllers = navigationController?.viewControllers{
var newStack = [UIViewController]()
if controllers.count > 0 {
let rootVC = controllers[0]
newStack.append(rootVC)
}
let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
if let vcB = mainStoryboard.instantiateViewController(withIdentifier: "ViewControllerBIdentifier") as? ViewControllerB
{
newStack.append(vcB)
}
if let vcD = mainStoryboard.instantiateViewController(withIdentifier: "ViewControllerDIdentifier") as? ViewControllerB
{
newStack.append(vcD)
}
navigationController?.setViewControllers(newStack, animated: true)
}
於是神奇的,可以由 C 橫出跳至 D 頁,按「<」鍵也是回到B而不是回到 A,真是個聰明的作法!
相信有上述的七種方法,大部份的切頁需求都可輕易達成!
也希望本篇的分享能對有切頁疑惑的開發者有一些幫助。
By Orange@iotec.tw, 2016/02/02