iOS13で検出シートが却下されました


113

iOS 13より前は、画面全体をカバーするために使用されるビューコントローラーが提示されていました。そして、却下されると、親ビューコントローラviewDidAppear機能が実行されました。

これで、iOS 13はビューコントローラーをデフォルトでシートとして表示します。つまり、カードは基になるビューコントローラーを部分的にカバーします。つまりviewDidAppear、親ビューコントローラーが実際に消えたことがないため、呼び出されません。

提示されたビューコントローラシートが却下されたことを検出する方法はありますか?ある種のデリゲートを使用するのではなく、親View Controllerオーバーライドできる他の関数?



それで、ルートvcに一度にすべてのモーダルシートを閉じる方法はありますか?
ウェズリー


なぜそれがいつ却下されたかを知る必要があるのですか?データをリロードしてUIを更新する場合は、通知またはKVOが適切な代替手段になる可能性があります。
martn_st

回答:


57

提示されたビューコントローラシートが却下されたことを検出する方法はありますか?

はい。

ある種のデリゲートを使用するのではなく、親View Controllerでオーバーライドできる他の関数はありますか?

いいえ。「ある種の委任者」はあなたがそれを行う方法です。自分でプレゼンテーションコントローラのデリゲートを作成し、オーバーライドしますpresentationControllerDidDismiss(_:)

https://developer.apple.com/documentation/uikit/uiadaptivepresentationcontrollerdelegate/3229889-presentationcontrollerdiddismiss


フルスクリーンであるかどうかに関係なく、提示されたView Controllerが却下されたことを通知する、実行時に生成される一般的なイベントがないことは、確かに厄介です。しかし、これは新しい問題ではありません。フルスクリーンでは表示されないビューコントローラが常に存在するためです。今(iOS 13では)もっとたくさんあります!私は他の場所でこのトピックに別の質問と回答を捧げます:Unified UIViewControllerは「最前線になりました」検出ですか?


6
これはたりない。提示されたVCにナバーがあり、プログラムでビューを閉じるカスタムバーボタンがある場合、プレゼンテーションコントローラーが閉じたのは呼び出されません。
イリーナ

14
こんにちは@ Irina-プログラムでビューを却下した場合、プログラムでビューを却下したため、コールバックは必要ありません—実行したために実行したことがわかります。デリゲートメソッドは、ユーザーが行う場合のみです。
マット

6
@matt回答ありがとうございます。ビューがプログラムで却下された場合、これは呼び出されません(Irinaが言うように)、そしてあなたは私たちがそれをしたことを私たちが知っていることは正しいです。iOS13の新しいモーダルプレゼンテーションスタイルで一種の「viewWillAppear」を取得するためだけに、不必要な量の定型コードを書く必要があると思います。ルーティングが抽出されるアーキテクチャ(MVVM +コーディネーター、またはVIPERのルータータイプなど)を介してルーティングを管理している場合は、特に厄介になります
AdamWaite19年

3
@AdamWaite同意しますが、この問題は新しいものではありません。この問題は、ポップオーバー、フルスクリーンでは表示されないビューコントローラ、アラートなどで何年にもわたって発生しています。私はこれをAppleの「イベント」のレパートリーにおける重大な欠陥だと考えています。私はただ現実とは何か、そしてその理由を言っているだけです。私はここで問題に直接取り組んでいます:stackoverflow.com/questions/54602662/…–
マット

1
presentationControllerDidDismiss(_ :)。子VCの戻るボタンをクリックしても呼び出されません。何か助けはありますか?
クリシュナミーナ

41

これは、シートとして表示される(つまり、デフォルトのiOS 13の方法で)子ビューコントローラーが閉じられたときに通知される親ビューコントローラーのコード例です。

public final class Parent: UIViewController, UIAdaptivePresentationControllerDelegate
{
  // This is assuming that the segue is a storyboard segue; 
  // if you're manually presenting, just see the delegate there.
  public override func prepare(for segue: UIStoryboardSegue, sender: Any?)
  {
    if segue.identifier == "mySegue" {
      segue.destination.presentationController?.delegate = self;
    }
  }

  public func presentationControllerDidDismiss(
    _ presentationController: UIPresentationController)
  {
    // Only called when the sheet is dismissed by DRAGGING.
    // You'll need something extra if you call .dismiss() on the child.
    // (I found that overriding dismiss in the child and calling
    // presentationController.delegate?.presentationControllerDidDismiss
    // works well).
  }
}

Jerland2の答えは()、元の質問者がシートがされたときに関数呼び出しを取得したいため、混同されて却下(彼は、ユーザーの試行と呼ばれるpresentationControllerDidAttemptToDismiss、実装のに対し、および失敗したシートを却下する)を、および(b)はisModalInPresentationを設定しますは完全に直交しており、実際には、提示されたシートを却下できなくなります(これは、OPが望んでいるものの反対です)。


6
これはうまく機能します。呼び出されたVCでnavコントローラーを使用する場合は、navコントローラーをpresentationController?、delegateとして割り当てる必要があるというヒントです(navがtopViewControllerとして持っているVCではありません)。
instAustralia

@instAustralia理由を説明したり、ドキュメントを参照したりできますか?ありがとう。
アーメドオサマ

presentationControllerDidDismissユーザーが戻るボタンを押したときに呼び出されるようにする方法は?
クリシュナミーナ

@ AhmedOsama-ナビゲーションコントローラーはプレゼンテーションコントローラーであり、解雇に対応するための代理人です。Nav Controllerに組み込まれているVCも試しましたが、ここに実際に閉じるボタンがあり、応答します。Appleのドキュメントで直接見つけることはできませんが、ここで参照されていますsarunw.com/posts/modality-changes-in-ios13
instAustralia

27

戻っviewWillAppearviewDidAppear設定される別のオプション

let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen

このオプションは全画面をカバーし、却下した後、上記のメソッドを呼び出します


2
PiterPanありがとうございます。これは機能しています。これは素晴らしく、最速の解決策です。
ErkamKUCET19年

以前のデフォルトの動作を復元するためのこの高速で信頼性の高い方法をありがとうございます。この修正を即座に実施してから、合理的な方法で新しい動作への移行を計画できるのは素晴らしいことです。
イアンラブジョイ

11
これは修正ではなく回避策です。誰もがiOS12スタイルシートに戻るのは良いことではありません。iOS 13のものはかっこいいです!:)
マット

1
iPadは、モーダルで表示される場合、デフォルトでページシートとして表示されるため、これをiPadに使用する場合は注意が必要です。これは、fullScreenとして現在にアプリを強制されます
wyu

私には効かない。モーダルコントローラーを開きます。却下して閉じますが、willAppearは呼び出されません。どうして?感謝
neo999

21

将来の読者のために、ここに実装に関するより完全な答えがあります:

  1. ルートビューコントローラーでセグエの準備をします(モーダルにナビゲーションコントローラーがあると仮定します)
    // Modal Dismiss iOS 13
    modalNavController.presentationController?.delegate = modalVc
  1. モーダルビューコントローラーで、次のデリゲート+メソッドを追加します
// MARK: - iOS 13 Modal (Swipe to Dismiss)

extension ModalViewController: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {


        print("slide to dismiss stopped")
        self.dismiss(animated: true, completion: nil)
    }
}
  1. デリゲートメソッドを呼び出すには、モーダルViewControllerで次のプロパティがtrueであることを確認してください
    self.isModalInPresentation = true
  1. 利益

1
self.isModalInPresentation = trueの場合、ドラッグの却下は機能しません。その行を削除すると、デリゲートメソッドは引き続きOKと呼ばれます。ありがとうございました。
YogeshPatel19年

2
これは、(a)元の質問者がシートが閉じられたときに関数呼び出しを取得したかったため(ユーザーがシートを閉じようとして失敗したときに呼び出されるpresentationControllerDidAttemptToDismissを実装した場合)、(b)isModalInPresentationを設定するために混乱しますは完全に直交しており、実際には、提示されたシートを却下できなくなります(これは、OPが望んでいるものの反対です)。
マット

1
@Mattの回答ポイントのフォローアップ(a):使用presentationControllerDidDismissは機能するはずです
gondo

5

迅速

呼び出しの一般解 viewWillAppeariOS13

class ViewController: UIViewController {

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            print("viewWillAppear")
        }

        //Show new viewController
        @IBAction func show(_ sender: Any) {
            let newViewController = NewViewController()
            //set delegate of UIAdaptivePresentationControllerDelegate to self
            newViewController.presentationController?.delegate = self
            present(newViewController, animated: true, completion: nil)
        }
    }

    extension UIViewController: UIAdaptivePresentationControllerDelegate {
        public func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
            if #available(iOS 13, *) {
                //Call viewWillAppear only in iOS 13
                viewWillAppear(true)
            }
        }
    }

1
これは、関数を呼び出すことによってではなく、上からのスライドを使用して却下を処理するだけdismiss(_)です。
ペドロパウロアモリム

3

DRAG OR CALL DISMISSFUNCは以下のコードで動作します。

1)ルートビューコントローラーで、次のコードのように、プレゼンテーションビューコントローラーがどれであるかを示します。

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "presenterID" {
        let navigationController = segue.destination as! UINavigationController
        if #available(iOS 13.0, *) {
            let controller = navigationController.topViewController as! presentationviewcontroller
            // Modal Dismiss iOS 13
            controller.presentationController?.delegate = self
        } else {
            // Fallback on earlier versions
        }
        navigationController.presentationController?.delegate = self

    }
}

2)再びルートビューコントローラーで、プレゼンテーションビューコントローラーが破棄されたときに何をするかを指示します

public func presentationControllerDidDismiss(
  _ presentationController: UIPresentationController)
{
    print("presentationControllerDidDismiss")
}

1)プレゼンテーションビューコントローラで、この画像のキャンセルまたは保存ボタンを押したとき。以下のコードが呼び出されます。

self.dismiss(animated: true) {
        self.presentationController?.delegate?.presentationControllerDidDismiss?(self.presentationController!)
    }

ここに画像の説明を入力してください


1
NavigationController.topViewControllerをpresentationViewControllerにキャストする必要がありますか?そうではないと思います
Fitsyu

[キャンセル]ボタンの子VCを閉じた後、親VCにデータを再読み込みするにはどうすればよいですか?
クリシュナミーナ

3

却下されているUIViewControllerでviewWillDisappearをオーバーライドします。isBeingDismissedブールフラグを介して解雇を警告します。

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if isBeingDismissed {
        print("user is dismissing the vc")
    }
}

**ユーザーがスワイプの途中でカードを上にスワイプすると、カードが閉じられていなくても、カードは閉じられたものとして登録されます。しかし、それはあなたが気にしないかもしれないエッジケースです。


どうですかself.dismiss(animated: Bool, completion: (() -> Void)?)
iGhost

0

誰かが提示ビューコントローラへのアクセス権を持っていない場合、彼らは単にビューコントローラを提示して、次のメソッドをオーバーライドして変更することができますmodalPresentationStylefullScreenたり、このアプローチで、上記の方法のいずれかを追加することができます

 override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    if let _ = viewControllerToPresent as? TargetVC {
        viewControllerToPresent.modalPresentationStyle = .fullScreen
    }
    super.present(viewControllerToPresent, animated: flag, completion: completion)
}

提示されたビューコントローラーがナビゲーションコントローラーであり、ルートコントローラーを確認したい場合は、上記の条件を次のように変更できます。

if let _ = (viewControllerToPresent as? UINavigationController)?.viewControllers.first as? TargetVC {
   viewControllerToPresent.modalPresentationStyle = .fullScreen
}

-2

フルスクリーンでModalPresentationStyleを使用した場合、コントローラーの動作は通常どおりに戻ります。

ConsultarController controllerConsultar = this.Storyboard.InstantiateViewController( "ConsultarController")as ConsultarController; controllerConsultar.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; this.NavigationController.PushViewController(controllerConsultar、true);


既存の回答を繰り返します。
マット

-3

私の見解では、AppleはpageSheetデフォルトを設定すべきではありませんmodalPresentationStyle

fullScreen使用してスタイルをデフォルトに戻したいswizzling

このような:

private func _swizzling(forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
    if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
       let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension UIViewController {

    static func preventPageSheetPresentationStyle () {
        UIViewController.preventPageSheetPresentation
    }

    static let preventPageSheetPresentation: Void = {
        if #available(iOS 13, *) {
            _swizzling(forClass: UIViewController.self,
                       originalSelector: #selector(present(_: animated: completion:)),
                       swizzledSelector: #selector(_swizzledPresent(_: animated: completion:)))
        }
    }()

    @available(iOS 13.0, *)
    private func _swizzledPresent(_ viewControllerToPresent: UIViewController,
                                        animated flag: Bool,
                                        completion: (() -> Void)? = nil) {
        if viewControllerToPresent.modalPresentationStyle == .pageSheet
                   || viewControllerToPresent.modalPresentationStyle == .automatic {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        _swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
    }
}

そして、この行をあなたの AppDelegate

UIViewController.preventPageSheetPresentationStyle()

1
これは独創的ですが、私はそれに同意できません。これはハッキーであり、さらに言えば、iOS13の粒度に反します。iOS13では「カード」プレゼンテーションを使用することになっています。Appleが私たちに期待する応答は「回避策」ではありません。それは「乗り越える」ことです。
マット

あなたの主張に同意しますが、このソリューションは、Appleが私たちに勧めているようなカードプレゼンテーションスタイルを使用するのに役立ちません。ただし、デフォルトのスタイルとして、それを設定すると、コードの間違いのどこかの既存の行を行いますので、presentingViewController意志はないトリガーviewWillAppear
ヤコブ

1
はい。ただし、私自身の回答ですでに述べたように、これはフルスクリーン以外のプレゼンテーション(iPadのポップオーバーやページ/フォームシートなど)では常に問題でした。したがって、これは新しいことではありません。それは今それがもっとあるということだけです。頼ることviewWillAppearはある意味で常に間違っていました。もちろん、私はAppleがやって来て、私の下から床を切り取るのは好きではありません。しかし、私が言うように、私たちはそれと一緒に暮らし、新しい方法で物事を行う必要があります。
マット

私のプロジェクトでは、View Controller(と呼ばれるpresentedController)がどこに表示されているのかわからず、正確に何であるかわからないシナリオがいくつかありますpresentingViewController。例:場合UIViewController.topMostViewController()によっては、現在のウィンドウで一番上のビューコントローラーを返すものを使用する必要があります。そのためviewWillAppear、ビューコントローラで正しい処理(データの更新、UI)を実行するために、現在の動作を維持するためにスウィズリングを実行したいのです。それを解決するためのアイデアがあれば、助けてください。
ヤコブ

ええと、私の答えの最後にリンクしている解決策はそれを解決するのに役立つと私は信じています。プレゼンテーション時に構成するには多少の作業が必要ですが、基本的には、提示されたView Controllerが閉じられたときに、すべてのプレゼンター(アラートのプレゼンターを含む)が聞くことを保証します。
マット

-5

presentingViewController.viewWillAppearを呼び出すのは簡単ではないでしょうか。却下する前に?

self.presentingViewController?.viewWillAppear(false)
self.dismiss(animated: true, completion: nil)

4
電話するのはあなたのものではありません。
マット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.