需求:A視圖控制器中
present
B視圖控制器,B視圖控制器再present
C視圖控制器。最后從C視圖控制器直接返回到A視圖控制器。
1. 一些解釋
1.1 兩個(gè)常用的方法
/**
* 展示模態(tài)視圖
*
* @param viewControllerToPresent 調(diào)轉(zhuǎn)的目標(biāo)控制器
* @param flag 如果flag == NO,那么不會(huì)執(zhí)行任何動(dòng)畫(當(dāng)然也不會(huì)執(zhí)行自定義動(dòng)畫);如果flag == YES,則有可能執(zhí)行自定義動(dòng)畫,如果沒有自定義動(dòng)畫則會(huì)執(zhí)行系統(tǒng)默認(rèn)動(dòng)畫。
* @param completion 跳轉(zhuǎn)完成后的回調(diào)
*/
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion;
/**
* 關(guān)閉模態(tài)視圖
*
* @param flag 如果flag == NO,那么不會(huì)執(zhí)行任何動(dòng)畫(當(dāng)然也不會(huì)執(zhí)行自定義動(dòng)畫);如果flag == YES,則有可能執(zhí)行自定義動(dòng)畫,如果沒有自定義動(dòng)畫則會(huì)執(zhí)行系統(tǒng)默認(rèn)動(dòng)畫。
* @param completion 關(guān)閉完成后的回調(diào)
*/
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion;
1.2 兩個(gè)名詞說明
// 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在哪里執(zhí)行?
我們最常使用的一種場(chǎng)景:A視圖控制器present
跳轉(zhuǎn)到B視圖控制器,B視圖控制器dismiss
回到A視圖控制器,所以會(huì)想當(dāng)然的以為dismissViewController
這個(gè)方法是在B視圖控制器里面執(zhí)行。
標(biāo)準(zhǔn)答案是:在A視圖控制器里執(zhí)行。
蘋果文檔有這么一段話:
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.
或者我們只要記住一個(gè)簡(jiǎn)單的原則:
誰污染,誰治理!
但是,在上述場(chǎng)景中,用B視圖控制器執(zhí)行也是可以的,因?yàn)橄到y(tǒng)會(huì)自動(dòng)優(yōu)化,當(dāng)B視圖控制器沒有present
過其他視圖控制器的時(shí)候,dismissViewController
方法會(huì)自動(dòng)交給B視圖控制器的presentingViewController
執(zhí)行,也就是A視圖控制器。
3. 多個(gè)模態(tài)視圖之間跳轉(zhuǎn)
回到我們前言的需求,如何從C視圖控制器直接返回到A視圖控制器?
在網(wǎng)上也看到一些解答,比如說利用通知讓B視圖控制器執(zhí)行dismissViewController
方法。其實(shí)這樣是不行的,原因剛剛已經(jīng)解釋過,對(duì)于一個(gè)視圖控制器X,它執(zhí)行dismissViewController
方法的時(shí)候?qū)?huì)關(guān)閉它present
的模態(tài)視圖,只有在它沒有present
過模態(tài)視圖的時(shí)候(但是這里B已經(jīng)present
到C了),才會(huì)交給他的presentingViewController
執(zhí)行dismissViewController
方法。所以這里如果交給B執(zhí)行,和直接在C里面執(zhí)行dismissViewController
方法的效果是一樣的。
顯然一個(gè)最簡(jiǎn)單的解決辦法就是利用通知或者代理,在A中執(zhí)行dismissViewController
方法。此時(shí)B和C視圖控制器會(huì)發(fā)生什么變化呢?
依然摘錄一段蘋果的文檔做一下解釋:
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.
也就是說,其實(shí)在present
多個(gè)視圖控制器的時(shí)候,系統(tǒng)維護(hù)了一個(gè)棧,以我們現(xiàn)在這個(gè)情況為例,從棧底到棧頂依次是A->B->C。當(dāng)棧中某個(gè)位置的視圖控制器執(zhí)行dismissViewController
方法的時(shí)候,棧中所有在它之上的視圖控制器都會(huì)被dismiss
,不同的是,棧頂?shù)囊晥D控制器將會(huì)以動(dòng)畫方式被dismiss
,而中間的視圖控制器只是簡(jiǎn)單的remove
掉。
這種方法是可行的,因?yàn)?dismissViewController
總是要在A方法中執(zhí)行的。不過這樣做會(huì)遇到代碼耦合的問題。
一種低耦合的解決方案:
// C視圖控制器觸發(fā)dismiss方法前添加這么一段代碼
UIViewController *rootVC = self.presentingViewController;
// rootVC.view.alpha = 0;
while (rootVC.presentingViewController) {
rootVC = rootVC.presentingViewController;
}
[rootVC dismissViewControllerAnimated:YES completion:nil];
在循環(huán)中連續(xù)獲取presentingViewController,于是最終可以得到根視圖控制器,這里就是A,不過這使得A視圖控制器中不用添加任何代碼,從而解決了耦合的問題。
這樣寫的另一個(gè)好處是,不管是多少個(gè)視圖控制器之間的跳轉(zhuǎn),都可以很方便的完成。
缺點(diǎn):你會(huì)明顯的感覺到是從C->B->A,有沒有一種完美的解決方案,讓用戶感覺直接從C->A?
這句代碼rootVC.view.alpha = 0;
的加入可以讓達(dá)到C->A的效果,但是Animated的設(shè)置就無效了。
還是求完美的解決方法。