一、了解present和dismiss
一個iOS開發(fā),這個控制器的打開和關閉,應該是接觸UIKit所接觸的第一個關于UIViewController的API,然而,你真的了解它嗎?
同樣的,本文默認你已經(jīng)了解UIViewController的present和dismiss方法,并多次運用。另外本文不再加OC語言的代碼,畢竟都能讀得懂swift。
二、誰來調(diào)用dismiss方法?
咱們從最基本的開始:一個控制器A,和一個控制器B。

讓A打開B,在A中是這樣寫:
//A
let B = ViewController()
self.present(B, animated: true, completion: nil)
在B中需要關閉B的時候在B中這么寫:
//B
self.dismiss(animated: true, completion: nil)
難道?這樣寫有問題?這....可能大多數(shù)開發(fā)者都是這么寫,也沒有出現(xiàn)過什么問題。但是...
YES,這樣寫在“理論”講,是錯誤的。
別急,在實際上,這么寫大多數(shù)情況是沒有問題的,因為iOS系統(tǒng)幫我們做了很多。
三、presentingViewController和presentedViewController
想了解為什么說那么寫在“理論上”是錯誤的,先從presentingViewController和presentedViewController說起。
為了不使本文枯燥,不引用大段描述和說明,只針對案例解釋。當從A中彈出B后:
- self.presentingViewController: 在A中,就是nil;在B中,就是A
- self.presentedViewController:在A中,就是B;在B中,就是nil
那為什么說在B中調(diào)用dismiss在“理論上”是錯的呢?因為有這么一個規(guī)則一定要記?。?/p>
誰污染,誰治理!
很熟悉對不對?MRC的法則,誰創(chuàng)建誰釋放!所以A打開了B,當然是A來負責關閉!
正確寫法是在A里調(diào)用self.dismiss(animated: true, completion: nil)。
等等,之前都是B里調(diào)用?沒錯,dismiss方法系統(tǒng)會自動優(yōu)化,當B視圖控制器調(diào)用dismiss時,它并沒有打開任何界面,就將dismissViewController方法會自動交給B的presentingViewController執(zhí)行,也就是A來執(zhí)行。
如果現(xiàn)在A沒有打開B的話,調(diào)用dismissViewController時它沒有presentedViewController,轉交給它的presentingViewController,但是它也沒有presentingViewController,所以dismiss就不執(zhí)行了。swift中可選型能很好的解釋了:
self.presentingViewController?.dismiss(animated: true, completion: nil) //presentingViewController為nil,后面的不執(zhí)行
擴展:關于實例為空的時候方法不執(zhí)行,可百度“iOS消息轉發(fā)”了解。
既然這樣,說這個有毛用?
如果你從A打開了B,從B打開了C,現(xiàn)在怎么直接回到A?
你很可能會通過一些手段讓B執(zhí)行dismiss方法,但你會得到錯誤的結果,因為你這里如果交給B執(zhí)行dismiss方法,和直接在C里面執(zhí)行dismiss方法的效果是一樣的,也就是說你到了B的界面并沒有到A。
SO,dismiss方法必須讓需要回到的這個控制器來執(zhí)行。那么一個控制器的presentingViewController一定是打開它的那個控制器嗎?繼續(xù)往后看。
四、presentingViewController是打開它的那個控制器嗎?
從上文,A打開了B,A是B的presentingViewController,那是不是所有的控制器的presentingViewController都是調(diào)用presnet方法打開自己的那個控制器呢?
NO!但是剛才A打開了B,不是說A是B的presentingViewController嗎?當然,不是說一個控制器A彈出一個控制器B,A就一定是B的presentingViewController。

別著急,看幾個圖片娛樂一下:

這時箭頭指向的控制器的presenting是誰?

這時箭頭指向的控制器的presenting是誰?
第一個圖片是navigationController,第二個是tabbarController。又跟你想的不一樣了?看來你真的了解的太少,這時咱們得了解一下子控制器了。
四、子控制器childViewControllers
剛才在了解presentingViewController和presentedViewController的時候,如果你用代碼試了一下,會發(fā)現(xiàn)parentViewController(OC中,swift叫parent),父控制器。有父就有子,每個控制器都有一個屬性,叫做childViewControllers,存放它的子控制器。這里對childViewControllers的使用方法使用場景使用優(yōu)勢不做探討。我們假設你了解并多次使用了子控制器來顯示復雜界面的。
但是實際上只要你學習iOS的UIKit,你就肯定已經(jīng)多次,甚至經(jīng)常使用了這個東西。
我們知道,在一個有導航控制器的控制器P中加入了子控制器Q,然后添加了Q的view,這個Q就可以使用self.navigationController進行跳轉,Q是利用的P的導航控制器進行跳轉。也就是說Q的關于控制器的操作都被P給處理了。
那新加入一個問題:如果在Q中present出來一個界面R,那這個R的presentingViewController是誰呢?
是的,是P。此時已經(jīng)出現(xiàn)了剛才說的問題,Q打開了R,但是R的presentingViewController卻是P。這時因為Q是P的子控制器。
那么就可以解釋上面兩個圖片的結果為什么是navi控制器和tabbar控制器了。因為使用標簽控制器和導航控制器,都是顯示的子控制器的內(nèi)容,一個帶導航欄,一個帶標簽欄。
所有的控制器都有childViewControllers屬性,保存了所有的子控制器,但是有些特殊的控制器,比如UIPageViewController、UINavigationController、UITabBarController等,還有一個viewControllers屬性,實際內(nèi)容和childViewControllers一樣。這些特殊的控制器實際上不展示主要內(nèi)容,主要內(nèi)容由子控制器展示。
所以剛才說,只要你學了UIKit用了UINavigationController和UITabBarController,你就在使用childViewControllers在做界面的管理了。
因此,當某個控制器有父控制器的時候,它的presentingViewController是父控制器的presentingViewController。
說這么多,又有毛用?

比如你做過轉場動畫,會用到這么個方法animateTransition(using transitionContext: UIViewControllerContextTransitioning),當然你首先要做的,就是從context里獲取toViewcontroller和fromViewController。如果你的這個轉場是個modalPresent,此時你獲取到的fromViewController可不一定是調(diào)用present的那個控制器,所以你要根據(jù)這個VC來做一些動畫,可能就要有問題了。
如果是帶UINavigationController,需要通過nav.topViewController獲取nav當前的控制器,如果是帶UITabBarController,需要通過tab.selectedViewController獲取tab當前的控制器。
四、如何強制指定presentingViewController就是打開自己的那個?
我們需要了解一下這兩個屬性:
@property(nonatomic,assign) BOOL definesPresentationContext;
@property(nonatomic,assign) UIModalPresentationStyle modalPresentationStyle;
modalPresentationStyle屬性決定了將要present的控制器以何種方式展現(xiàn),默認值為UIModalTransitionStyleCoverVertical。如果把一個控制器的definesPresentationContext屬性設置為YES,那么在需要進行UIModalPresentationCurrentContext類型的跳轉的時候,UIKit會使用視圖層級內(nèi)的這個控制器來進行跳轉。
avc.definesPresentationContext = false
avc.modalPresentationStyle = .currentContext
大功告成!現(xiàn)在presentingViewController能夠獲取到我們期望的對象了。