需求:A視圖控制器中
present
B視圖控制器,B視圖控制器再present
C視圖控制器。最后從C視圖控制器直接返回到A視圖控制器。
1. 一些解釋
1.1 兩個常用的方法
/**
* 展示模態視圖
*
* @param viewControllerToPresent 調轉的目標控制器
* @param flag 如果flag == NO,那么不會執行任何動畫(當然也不會執行自定義動畫);如果flag == YES,則有可能執行自定義動畫,如果沒有自定義動畫則會執行系統默認動畫。
* @param completion 跳轉完成后的回調
*/
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion;
/**
* 關閉模態視圖
*
* @param flag 如果flag == NO,那么不會執行任何動畫(當然也不會執行自定義動畫);如果flag == YES,則有可能執行自定義動畫,如果沒有自定義動畫則會執行系統默認動畫。
* @param completion 關閉完成后的回調
*/
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion;
1.2 兩個名詞說明
// The view controller that was presented by this view controller or its nearest ancestor.
@property(nullable, nonatomic,readonly) UIViewController *presentedViewController NS_AVAILABLE_IOS(5_0);
// The view controller that presented this view controller (or its farthest ancestor.)
@property(nullable, nonatomic,readonly) UIViewController *presentingViewController NS_AVAILABLE_IOS(5_0);
Apresent
B,A是presentedViewController,B是presentingViewController。
2. dismissViewController在哪里執行?
我們最常使用的一種場景:A視圖控制器present
跳轉到B視圖控制器,B視圖控制器dismiss
回到A視圖控制器,所以會想當然的以為dismissViewController
這個方法是在B視圖控制器里面執行。
標準答案是:在A視圖控制器里執行。
蘋果文檔有這么一段話:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
或者我們只要記住一個簡單的原則:
誰污染,誰治理!
但是,在上述場景中,用B視圖控制器執行也是可以的,因為系統會自動優化,當B視圖控制器沒有present
過其他視圖控制器的時候,dismissViewController
方法會自動交給B視圖控制器的presentingViewController
執行,也就是A視圖控制器。
3. 多個模態視圖之間跳轉
回到我們前言的需求,如何從C視圖控制器直接返回到A視圖控制器?
在網上也看到一些解答,比如說利用通知讓B視圖控制器執行dismissViewController
方法。其實這樣是不行的,原因剛剛已經解釋過,對于一個視圖控制器X,它執行dismissViewController
方法的時候將會關閉它present
的模態視圖,只有在它沒有present
過模態視圖的時候(但是這里B已經present
到C了),才會交給他的presentingViewController
執行dismissViewController
方法。所以這里如果交給B執行,和直接在C里面執行dismissViewController
方法的效果是一樣的。
顯然一個最簡單的解決辦法就是利用通知或者代理,在A中執行dismissViewController
方法。此時B和C視圖控制器會發生什么變化呢?
依然摘錄一段蘋果的文檔做一下解釋:
If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
也就是說,其實在present
多個視圖控制器的時候,系統維護了一個棧,以我們現在這個情況為例,從棧底到棧頂依次是A->B->C。當棧中某個位置的視圖控制器執行dismissViewController
方法的時候,棧中所有在它之上的視圖控制器都會被dismiss
,不同的是,棧頂的視圖控制器將會以動畫方式被dismiss
,而中間的視圖控制器只是簡單的remove
掉。
這種方法是可行的,因為 dismissViewController
總是要在A方法中執行的。不過這樣做會遇到代碼耦合的問題。
一種低耦合的解決方案:
// C視圖控制器觸發dismiss方法前添加這么一段代碼
UIViewController *rootVC = self.presentingViewController;
// rootVC.view.alpha = 0;
while (rootVC.presentingViewController) {
rootVC = rootVC.presentingViewController;
}
[rootVC dismissViewControllerAnimated:YES completion:nil];
在循環中連續獲取presentingViewController,于是最終可以得到根視圖控制器,這里就是A,不過這使得A視圖控制器中不用添加任何代碼,從而解決了耦合的問題。
這樣寫的另一個好處是,不管是多少個視圖控制器之間的跳轉,都可以很方便的完成。
缺點:你會明顯的感覺到是從C->B->A,有沒有一種完美的解決方案,讓用戶感覺直接從C->A?
這句代碼rootVC.view.alpha = 0;
的加入可以讓達到C->A的效果,但是Animated的設置就無效了。
還是求完美的解決方法。