iOS13以降でviewWillAppearやviewDidAppearが呼ばれない場合の対処法
例えば ViewController AからViewController Bを表示してデータの値を書き換える。そしてViewController Bでの作業が終わって閉じたらViewController AのviewWillAppear
やviewDidAppear
で新しいデータの値を表示する(tableview.reloadData()
など)のは良くあるパターン。
今更な話なのだがiOS12までは動いていたこのコードがそのままだとiOS13以降では動かなくなることがある。これで何度かつまずいたので簡単にまとめておく。
結論から
まずは下記を確認。UIAdaptivePresentationControllerDelegate
を使って今までのコードを書き換える。
これで何故動かなくなったのか?どうすれば良いのか?がわかるはず。
以下、いくつかの解決方法について簡単に説明。
フルスクリーン表示にしてしまう
1つ目はViewController Bをフルスクリーンにしてしまう方法。これで今まで通りにViewController AのviewWill/DidAppear
が呼ばれる。
iOS13以降は今までのように何も考えずに新しい画面を表示するとシートタイプの画面が表示されてviewWill/DidAppear
が呼ばれないので、これを全画面表示にしてiOS12以前の挙動と同じにするようにする。
StoryboardならSegueの設定でPresent Modally
とFull Screen
を設定。
コードの場合はSecondViewController
を開くとして以下のようになる。
secondViewController.modalPresentationStyle = .fullScreen
present(secondViewController, animated: true, completion: nil)
今までのコードがうまく動かなくなったと言う事は、元々新しい画面はフルスクリーンで表示されていたので違和感はないはず。
だが、すでにiOS14が出ている現在ではユーザには今時の挙動と少々違って感じるかもしれない。
スワイプで閉じられなくする
isModalInPresentation
と言うプロパティがあるので、ViewController Bでこれをfalse
にしてスワイプで画面を閉じられなくする。必然的にunwind
やdismiss
などコードやSegueで画面を閉じてViewController Aに戻らなければならないので、そこで閉じるイベントをViewController Aに伝えるかデータを再読み込みするコードを書いておく。
が、見た目は今時のシートなのにスワイプで閉じられないのはユーザに違和感を与える可能性が高い。
UIAdaptivePresentationControllerDelegateを使う
前出のビデオとサンプルコードでやっている方法。
シートをスワイプで閉じて良いかどうかなどの確認をUIAdaptivePresentationControllerDelegate
を通じて行うので、これをViewController Bで受け取ってViewController Bが閉じるタイミングを検知。それをプロトコルでViewController Aへ伝える。
具体的にはこんな感じ。
とりあえずUIAdaptivePresentationControllerDelegate
の部分。
各種イベントを受け取ってsheetが閉じた時にdelegate
になっている元のViewController AのviewDidDismiss
を呼び出している。ここでViewController Aがデータのアップデートを行う。
詳細はサンプルコードを確認のこと。
extension ThirdViewController: UIAdaptivePresentationControllerDelegate {
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
print("\(type(of: self)): \(#function)")
return true
}
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
print("\(type(of: self)): \(#function)")
}
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
print("\(type(of: self)): \(#function)")
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("\(type(of: self)): \(#function)")
delegate?.viewDidDismiss()
}
}
どれが良いのか?
新しいアプリならUIAdaptivePresentationControllerDelegate
を使うのが今風で良いと思う。が、既存のアプリの場合はフルスクリーンにするかUIAdaptivePresentationControllerDelegate
かのどちらかをUXとの兼ね合いで決めると良いだろう。
コード
フルスクリーンにする方法とUIAdaptivePresentationControllerDelegate
を使う方法を試したサンプルをGitHubにあげておいた。
それぞれの方法でどの順番で何が呼ばれるかを表示している。
ちなみにログは下記のようになる。
// 起動時Viewが表示される
ViewController: viewWillAppear(_:)
ViewController: viewDidAppear(_:)
// Full Screen クリックてSecondViewが表示される
SecondViewController: viewWillAppear(_:)
SecondViewController: viewDidAppear(_:)
// backをクリックして元のViewが表示される
ViewController: unwindToFirstViewController(_:)
ViewController: viewWillAppear(_:)
ViewController: viewDidAppear(_:)
// sheetをクリックしてThirdViewが表示される
ThirdViewController: viewWillAppear(_:)
ThirdViewController: viewDidAppear(_:)
// sheetをドラッグして閉じViewに戻る
ThirdViewController: presentationControllerShouldDismiss(_:)
ThirdViewController: presentationControllerWillDismiss(_:)
ThirdViewController: presentationControllerDidDismiss(_:)
ViewController: viewDidDismiss()
最後に
いまだに「あれ?viewWillAppearが呼ばれない?」なんてやっているので、簡単に思い出せるようにメモ。
これに限らずこう言うことが増えているのでマメにメモしていかないといけない。
Discussion