UIAlertController 教程:讓你輕鬆在 UIViewController 以外的地方呈現警告


從 iOS 8.0 開始加入的 UIAlertController,大概是大多數人在想要呈現 (present) 警告或者選單時的第一選擇。它的 API 非常的簡單,使用起來就像這樣:

我們可以發現,它其實就是一個普通的 UIViewController 子類型,需要用另一個 view controller 去呈現。但是,如果我們想要在 view controller 以外的地方呈現 UIAlertController 的話呢?比方說,當我們在 AppDelegate 裡的 application(\_:open:options:),接收到一個無效的 URL 時,我們可能會想要顯示一個警告。然而,AppDelegate 並沒有 present(\_:animated:completion:) 可以用,這時我們可以怎麼去呈現 UIAlertController 呢?

如果 self 不是 view controller 的話,最直覺的解決方法大概是直接找一個 view controller 來用吧!在 AppDelegate 裡,我們可以透過主視窗的 rootViewController 屬性來取得它的根 view controller。也就是說,我們可以這樣做:

然而,這個做法很快就會碰到問題:如果 rootViewController 已經呈現了別的 view controller 的話,alertController 是沒辦法顯示出來的,因為一個 view controller 一次只能呈現一個 view controller。要怎麼解決呢?我們有三種替代方案,一起來看看吧!

方法一:全部 dismiss 掉

呼叫 rootViewController.dismiss(animated: true) 把所有被呈現的 view controller 都去除 (dismiss) 掉,只留下 rootViewController 跟它的子 view controller:

這種方法的副作用是會破壞掉整個 app 的呈現階層。如果不想要這樣的話,可以選用方法二或方法三。

方法二:使用 topViewController

找到呈現層級最高的 view controller,並用它來呈現警告。後者可以透過一個 extension 來實作:

接著,再將剛剛的 window?.rootViewControllerwindow?.topViewController 取代掉就可以了:

方法三:開新視窗

第三種方法可謂是最厲害的大絕招。我們可以換一種思路,直接開一個新的視窗來專門呈現這一個警告:

使用這個方法,我們可以完全不管主視窗的 view controller 呈現階層,只要確定警告所在的視窗沒有被主視窗擋住就可以了。實際上的用法如下:

是不是更簡潔了呢?更棒的是,由於 presentAlert(alertController:) 是一個自由函式,它是真的可以被用在任何有 import UIKit 的地方的!舉例來說:

當然,我們也可以把 presentAlert(alertController:) 寫成 UIAlertController 的成員,其效果跟自由函式版本是一樣的:

看到這裡,你可能會有個疑惑:當使用者按下警告裡的「知道了」等動作,將警告去除掉之後,我們所創造出來的 alertWindow 要怎麼處理呢?答案是:不需要處理!原來,雖然一般的 UIView 在被加入 view 階層之後就會自動被 view 階層保留住 (retain),所以不需要手動保留,但 UIWindow 在顯示出來之後並不會被自動保留。也就是說,如果我們創造 UIWindow 的實例出來後,沒有把它指派到類型的屬性裡面的話,它是會在它被創造的那個方法結束之後自動消滅。

但既然 UIWindow 會自動消滅,那為什麼我們的 alertWindow 沒有在 present() 方法結束後馬上消失呢?這就要說到 UIWindow 的另一個奇妙特性了:它本身不會被自動保留,除非它的根 view controller 有呈現任何的 view controller。 也就是說,如果它的 rootViewController?.presentedViewController != nil 的話,它就會持續顯示;而一旦 rootViewController?.presentedViewController == nil,它就會馬上消失掉並且被摧毀。這樣的特性讓我們可以在前面寫的 alertController.present() 裡面省略掉手動保留 alertWindow 的步驟,因為只要 alertController 消失掉,alertWindow 也會跟著被摧毀。

結論

這篇文章介紹了三個從 UIViewController 以外的地方呈現警告的方法 ── 警告其實不限於用 UIAlertController 來做,也可以用任何的 UIViewController 來代替 ── 第一個方法會讓 app 回到最初的根 view controller,而第二個則不會,但這兩個方法都需要去存取主視窗。而第三個方法就不需要存取主視窗,因為我們是直接創造一個新視窗來呈現警告。

不過,最後還是要強調,雖然我們獲得了在任何有 UIKit 的地方都可以開新視窗來顯示警告的能力,但不代表我們就要濫用這個能力。比如之前舉的例子,讓 UIImageView 去顯示「無效的 Data」警告,就是讓一個普通的 view 也獲得了創造視窗的能力,而這很可能違反了 MVC 的分工原則,讓 view 的職責變得太複雜。畢竟,方便所帶來的不一定是簡單,所以越是方便的方法,越是要小心使用。比如說,限制自己只能在 AppDelegateUIViewController 等 controller 物件裡顯示警告,就是個好開始。


iOS 開發者、寫作者、filmmaker。現正負責開發 Storyboards by narrativesaw 此一故事板文件 app 中。深深認同 Swift 對於程式碼易讀性的重視。個人網站:lihenghsu.com。電郵:[email protected]

blog comments powered by Disqus
訂閲電子報

訂閲電子報

AppCoda致力於發佈優質iOS程式教學,你不必每天上站,輸入你的電子郵件地址訂閱網站的最新教學文章。每當有新文章發佈,我們會使用電子郵件通知你。

已收你的指示。請你檢查你的電郵,我們已寄出一封認證信,點擊信中鏈結才算完成訂閱。

Shares
Share This