你真的了解iOS中控制器的present和dismiss嗎?

一、了解present和dismiss

一個iOS開發(fā),這個控制器的打開和關閉,應該是接觸UIKit所接觸的第一個關于UIViewController的API,然而,你真的了解它嗎?
同樣的,本文默認你已經(jīng)了解UIViewController的presentdismiss方法,并多次運用。另外本文不再加OC語言的代碼,畢竟都能讀得懂swift。

二、誰來調(diào)用dismiss方法?

咱們從最基本的開始:一個控制器A,和一個控制器B。


A present 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

想了解為什么說那么寫在“理論上”是錯誤的,先從presentingViewControllerpresentedViewController說起。

為了不使本文枯燥,不引用大段描述和說明,只針對案例解釋。當從A中彈出B后:

  1. self.presentingViewController: 在A中,就是nil;在B中,就是A
  2. 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。

說這么多,又有毛用?

image.png

比如你做過轉場動畫,會用到這么個方法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能夠獲取到我們期望的對象了。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容