【Swift UIKit】モーダルが閉じた時に親のViewControllerで検知する方法!

この記事からわかること
- SwiftのUIKitでモーダルを閉じたことを検知する方法
- viewWillAppearメソッドで検知する
- presentationControllerDidDismissメソッドで検知する
- iOS12とiOS13で変更されたfullScreen
- モーダルの場合は親側のメソッドを子側から呼び出せない?
index
[open]
\ アプリをリリースしました /
SwiftのUIKitフレームワークでアプリ開発している場合にモーダル画面から親の画面に戻ったことを検知する方法や親ビューを再描画する方法をまとめていきます。モーダルの実装方法については下記記事を参考にしてください。
モーダルが閉じたことを親ビューから検知する
SwiftのUIKitでモーダルが閉じられたことを親のViewControllerから検知する方法は2種類あります。
- viewWillAppearメソッドで検知する
- presentationControllerDidDismissメソッドで検知する
viewWillAppearメソッドで検知する
UIViewControllerクラスの持つviewWillAppearメソッド
はビューが表示される直前に呼び出されるメソッドです。このメソッドを使用することでモーダルから遷移元の画面に戻った(ビューが再表示される)ことを検知することができます。
使用するにはviewWillAppear
メソッドをoverrideして定義してその中に実行したい処理があれば記述するだけです。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("モーダルから戻ったよ")
}
@IBAction func showModal() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let modalVC = storyboard.instantiateViewController(withIdentifier: "modal")
modalVC.modalPresentationStyle = .fullScreen
present(modalVC, animated: true, completion: nil)
}
}
しかしこの方法には注意点があります。
fullScreenでないと検知できない
モーダルの画面遷移を行う際はmodalPresentationStyle
プロパティに値を渡すことでモーダル画面のスタイルを変更することができます。この値が親の画面を完全に覆い尽くすfullScreen
でないとviewWillAppear
では検知することができません。
@IBAction func showModal() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let modalVC = storyboard.instantiateViewController(withIdentifier: "modal")
modalVC.modalPresentationStyle = .fullScreen
present(modalVC, animated: true, completion: nil)
}
例えばこの値が画面を完全に覆い尽くさないformSheet
などの場合だと検知することができません。この場合は親ビューの再描画が行われていないからです。
modalVC.modalPresentationStyle = .formSheet

またiOS12以前では明示的に指定しない場合、自動的にfullScreen
が適応されていましたが、iOS13以降ではformSheetに変更になっているので注意してください。
presentationControllerDidDismissメソッドで検知する
公式リファレンス:UIAdaptivePresentationControllerDelegate
UIAdaptivePresentationControllerDelegateクラスのpresentationControllerDidDismiss
メソッドはプレゼンテーション(モーダル)が閉じられたことを検知するメソッドです。この方法の場合はステップ数が少し多いです。
- 親にUIAdaptivePresentationControllerDelegateを継承
- 親から子のVCを取得し、delegateプロパティに親自身をセット
- presentationControllerDidDismissメソッドを定義
- 子のdismissメソッドの前に親のpresentationControllerのメソッドを呼び出す
まずは親にUIAdaptivePresentationControllerDelegate
を継承させます。子ビューを取得したらpresentationController
のdelegate
プロパティに自身をセットします。presentationControllerDidDismiss
には必要な処理を記述しておきます。
import UIKit
class ViewController: UIViewController,UIAdaptivePresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func showModal() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let modalVC = storyboard.instantiateViewController(withIdentifier: "modal")
modalVC.modalPresentationStyle = .formSheet
modalVC.presentationController?.delegate = self
present(modalVC, animated: true, completion: nil)
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("モーダルから戻ったよ")
}
}
子側ではdismiss
メソッドを呼び出して戻る前に親のpresentationControllerDidDismiss
メソッドを呼び出しています。
import UIKit
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func dismissModal() {
if let presentationController = presentationController{
presentationController.delegate?.presentationControllerDidDismiss?(presentationController)
}
print("dismissされていないよ")
self.dismiss(animated: true,completion: nil)
}
}
この方法は戻ったことを検知しているように見えますが、呼び出されるタイミングは以下のようになります。
モーダルから戻ったよ
dismissされていないよ
ですがこの方法ならfullScreen
でなくても一応モーダルから戻ったことを検知?することができました。
モーダルの場合は親側のメソッドを子側から呼び出せない?
最初は親側に適当なメソッドを用意して子側から親を取得して親のメソッドを呼び出そうと思いましたが、うまくいきませんでした。
import UIKit
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
func parentFunction(){
print("モーダルから戻ったよ")
}
@IBAction func showModal() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let modalVC = storyboard.instantiateViewController(withIdentifier: "modal")
modalVC.modalPresentationStyle = .formSheet
present(modalVC, animated: true, completion: nil)
}
}
import UIKit
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func dismissModal() {
let parent = self.parent as! ViewController
parent.parentFunction()
self.dismiss(animated: true,completion: nil)
}
}
この方法の場合はビルドすることはできたのですが、実行時に以下のようなエラーが発生してしまいました。
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
モーダルはビュー階層は異なる?
parent
プロパティは自身の親となるViewControllerが格納されるプロパティなのでここから親のメソッドを実行できるかと思いましたが、モーダル遷移の場合格納されていたのはnil
でした。
私は最初1つ目のNavigation Controllerのようなビュー階層でモーダルビューが管理されていると思いましたが、どうやら2つ目のように別々の階層で管理されているようです。
// ×
├── ViewController
│ └── ModalViewController
// ○?
├── ViewController
├── ModalViewController
そのためdelegateを使ってのメソッドの実行方法になりました。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。