78
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【iOS13】.pageSheetなmodalのdismiss時のライフサイクル・制御について

Last updated at Posted at 2019-11-17

iOS13~は、デフォルトでハーフモーダル的な挙動(modalPresentationStyle = .pageSheet)が追加されており、スワイプで閉じれるようになった。
それ用にデリゲートメソッドも複数用意されており、用途に応じて使い分けることで、誤操作の防止やモーダル表示元のViewControllerへイベントの伝搬ができる。

追加されたUIAdaptivePresentationControllerDelegateのインスタンスメソッド

  • presentationControllerDidAttemptToDismiss(_:)
  • presentationControllerShouldDismiss(UIPresentationController) -> Bool
  • presentationControllerWillDismiss(UIPresentationController)
  • presentationControllerDidDismiss(UIPresentationController)

.pageSheetなmodalをスワイプで閉じる時のライフサイクル

デリゲートメソッドを使う前に、そのデリゲートメソッドがいつ呼ばれるのか、dismiss周りのライフサイクルについて理解しておく必要がある。
WWDC19のスライドのこの図が一番分かりやすい

iOS13_modal.png

  • isModalPresentationがtrueなら DidAttemptToDismiss が呼ばれる
  • isModalPresentationがfalse(および設定していない) 場合、
    • ShouldDismiss -> WillDismiss -> DidDismissが呼ばれる

補足(isModalPresentationについて)

iOS13~用意された、UIViewControllerのプロパティ。
trueの場合、インタラクティブにmodalyなViewControllerがdismissされないようになる。

要は下スワイプしてもモーダルが閉じなくなる。
その下スワイプ時のイベントをキャッチして dismiss(animated: true) を実行したり、ダイアログを出したいときに使えるデリゲートメソッドが DidAttemptToDismiss

isModalPresentationのドキュメント

The default value of this property is false. When you set it to true, UIKit ignores events outside the view controller's bounds and prevents the interactive dismissal of the view controller while it is onscreen.

.pageSheetなmodalの下スワイプ時の制御パターンは2つ

前提として、
:warning: .pageSheetなmodalがdismissしたときは、表示元のVCのviewWillAppearが呼ばれないので、スワイプしてモーダルを閉じたときに、表示元VCにイベントなり値を渡す必要がある場合、上述したデリゲートメソッドを使って制御する必要がある。

(WWDC19のスライドより)
image.png

実装の仕方は大きく2通りに分かれる

  1. .pageSheetなmodalのdismissの挙動を制御しない場合
  2. .pageSheetなmodalのdismissの挙動を制御する場合(確認ダイアログを出すなど)

1.1 .pageSheetなmodalのdismissの挙動を制御しない場合

.isModalPresentation を指定しない。
ShouldDismiss -> WillDismiss -> DidDismiss(iOS13~)が呼ばれるので、WillDismiss or DidDismissのタイミングで親のViewControllerにデリゲートやクロージャでイベントを渡す

protocol EditViewControllerDelegate: class {
    func editViewControllerDidCancel(_ editViewController: EditViewController)
    func editViewControllerDidFinish(_ editViewController: EditViewController)
}

extension EditViewController: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        // 表示元のVCでDelegateに準拠してtableViewをreloadしたり、値渡しする
        self.delegate?.editViewControllerDidFinish(self)
    }
}

(追記) 1.2 .pageSheetなmodalのdismissの挙動を制御しない場合

上記の例でDidDismissを使用した場合、完全にモーダルが閉じ切ってからデリゲートメソッドが呼ばれるため、後続の処理によってはテンポが遅い場合がある。
その場合は、 DidAttemptToDismiss(_:) 内で dismiss を実行し親のViewControllerにデリゲートやクロージャでイベントを渡した方が体感0.5sほどテンポが早い

isModalInPresentation = true

func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
    // dismissを実行
    dismiss(animated: true)
    // 表示元のVCでDelegateに準拠してtableViewをreloadしたり、値渡しする
    self.delegate?.editViewControllerDidFinish(self)
}

2 .pageSheetなmodalのdismissの挙動を制御する場合(Appleのサンプル抜粋)

Appleのサンプル

.isModalPresentation を使う。
presentationControllerDidAttemptToDismiss(_:) デリゲートメソッドを使う。

サンプルの挙動

  1. モーダル内でテキストを編集していたときに、モーダルを下スワイプすると編集破棄の確認ダイアログを出す。
  2. 確認ダイアログ内でOKをタップすると dismiss(animated: true) を実行する
  • 表示元VCへの値渡し、イベントの伝搬もこのタイミングで行う
// 1: オリジナルのテキストと編集中のテキストの差異をBool値で返す
var hasChanges: Bool {
    return originalText != editedText
}

override func viewWillLayoutSubviews() {    
    textView.text = editedText
    
// 2: isModalInPresentationに1: のプロパティを代入
    let hasChanges = self.hasChanges
    isModalInPresentation = hasChanges
    saveButton.isEnabled = hasChanges
}

// 3: isModalInPresentationがtrueの場合、DidAttemptToDismissが呼ばれる
// メソッド内で確認ダイアログを出す
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
    // The user pulled down with unsaved changes
    // Clarify the user's intent by asking whether they intended to cancel or save
    confirmCancel(showingSave: true)
}

まとめ

新モーダルのdismissの挙動については、下記を抑えておく必要がある

  • isModalPresentationで大きくライフサイクルが変わること
  • dismissしても表示元VCのviewWillAppearが呼ばれないこと

参考URL

WWDC19 - Modernizing Your UI for iOS 13
https://developer.apple.com/videos/play/wwdc2019/224

公式サンプル
https://developer.apple.com/documentation/uikit/view_controllers/disabling_pulling_down_a_sheet

78
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
78
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?