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

この記事からわかること

  • SwiftUIKitモーダル閉じたことを検知する方法
  • viewWillAppearメソッドで検知する
  • presentationControllerDidDismissメソッドで検知する
  • iOS12iOS13変更されたfullScreen
  • モーダルの場合は親側のメソッドを子側から呼び出せない?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

SwiftのUIKitフレームワークでアプリ開発している場合にモーダル画面から親の画面に戻ったことを検知する方法親ビューを再描画する方法をまとめていきます。モーダルの実装方法については下記記事を参考にしてください。

モーダルが閉じたことを親ビューから検知する

SwiftのUIKitでモーダルが閉じられたことを親のViewControllerから検知する方法は2種類あります。

  1. viewWillAppearメソッドで検知する
  2. 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
Swift UIのsheetモディファイアを使用してモーダルウィンドウを実装している画面

またiOS12以前では明示的に指定しない場合、自動的にfullScreenが適応されていましたが、iOS13以降ではformSheetに変更になっているので注意してください。

presentationControllerDidDismissメソッドで検知する

公式リファレンス:UIAdaptivePresentationControllerDelegate

UIAdaptivePresentationControllerDelegateクラスのpresentationControllerDidDismissメソッドはプレゼンテーション(モーダル)が閉じられたことを検知するメソッドです。この方法の場合はステップ数が少し多いです。

  1. 親にUIAdaptivePresentationControllerDelegateを継承
  2. 親から子のVCを取得し、delegateプロパティに親自身をセット
  3. presentationControllerDidDismissメソッドを定義
  4. 子のdismissメソッドの前に親のpresentationControllerのメソッドを呼び出す

まずは親にUIAdaptivePresentationControllerDelegateを継承させます。子ビューを取得したらpresentationControllerdelegateプロパティに自身をセットします。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プロパティ

parentプロパティは自身の親となるViewControllerが格納されるプロパティなのでここから親のメソッドを実行できるかと思いましたが、モーダル遷移の場合格納されていたのはnilでした。

私は最初1つ目のNavigation Controllerのようなビュー階層でモーダルビューが管理されていると思いましたが、どうやら2つ目のように別々の階層で管理されているようです。

// ×
├── ViewController
│      └── ModalViewController

// ○?
├── ViewController
├── ModalViewController

そのためdelegateを使ってのメソッドの実行方法になりました。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index