提示されたViewControllerが閉じられたことを検出します


82

たとえば、VC2というビューコントローラクラスのインスタンスがあるとします。VC2には、それ自体を閉じる「キャンセル」ボタンがあります。しかし、「キャンセル」ボタンがトリガーされたときに、コールバックを検出または受信できません。VC2はブラックボックスです。

ビューコントローラ(VC1と呼ばれる)は、presentViewController:animated:completion:メソッドを使用してVC2を表示します。

VC2が却下されたときにVC1はどのようなオプションを検出する必要がありますか?

編集:@rory mckinnelのコメントと@NicolasMiariの回答から、私は次のことを試しました:

VC2の場合:

-(void)cancelButton:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:^{

    }];
//    [super dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

VC1の場合:

//-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
- (void)dismissViewControllerAnimated:(BOOL)flag
                           completion:(void (^ _Nullable)(void))completion
{
    NSLog(@"%s ", __PRETTY_FUNCTION__);
    [super dismissViewControllerAnimated:flag completion:completion];
//    [self dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

しかし、dismissViewControllerAnimatedVC1のは呼び出されていませんでした。


2
VC1では、viewWillAppearメソッドが呼び出されます
Istvan

1
ドキュメントによると、提示するコントローラーは実際の解雇に責任があります。提示されたコントローラーが自分自身を却下すると、プレゼンターにそれを行うように依頼します。したがってdismissViewControllerAnimated、VC1コントローラーでオーバーライドすると、VC2でキャンセルを押したときに呼び出されると思います。却下を検出してから、実際の却下を行うスーパークラスバージョンを呼び出します。
Rory McKinnel 2015

1
を呼び出すことでオーバーライドをテストできます[self.presentingViewController dismissViewControllerAnimated]。内部コードには、プレゼンターに却下を要求するための異なるメカニズムがある可能性があります。
Rory McKinnel 2015

@RoryMcKinnel:self.presentingViewControllerの使用は、私のラボVC2でも、実際のブラックボックスでも機能しました。あなたが答えにあなたのコメントを入れたら、私はそれを答えとして選びます。ありがとう。
user523234 2015

これに対する解決策は、この関連記事にあります:stackoverflow.com/a/34571641/3643020
Campbell_Souped

回答:


64

ドキュメントによると、提示するコントローラーは実際の解雇に責任があります。提示されたコントローラーが自分自身を却下すると、プレゼンターにそれを行うように依頼します。したがって、VC1コントローラーでdismissViewControllerAnimatedをオーバーライドすると、VC2でキャンセルを押したときに呼び出されると思います。却下を検出してから、実際の却下を行うスーパークラスバージョンを呼び出します。

議論からわかるように、これはうまくいかないようです。むしろ代わりに呼び出すのでは、根底にあるメカニズムに頼るよりも、dismissViewControllerAnimated:completionVC2自体、コールでdismissViewControllerAnimated:completionself.presentingViewControllerVC2インチ これにより、オーバーライドが直接呼び出されます。

全体としてより良いアプローチは、モーダルコントローラーが完了したときに呼び出されるブロックをVC2に提供させることです。

したがって、VC2では、ブロックプロパティsayに名前を指定しonDoneBlockます。

VC1では、次のように提示します。

  • VC1で、VC2を作成します

  • VC2のdoneハンドラーを次のように設定します。 VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • [self presentViewController:VC2アニメーション:YES完了:nil]を使用して、VC2コントローラーを通常どおりに提示します。

  • VC2では、ターゲットアクションのキャンセル呼び出しで self.onDoneBlock();

その結果、VC2は、それを上げた人に、それが完了したことを通知します。を拡張onDoneBlockして、モーダルが完了したか、キャンセルされたか、成功したかなどを示す引数を持つことができます。


2
これがどれほど美しく機能するかを感謝し、感謝したいだけです。4年後でも!ありがとうございました!
アンナ

48

この目的に使用できるUIViewControllerと呼ばれる特別なブール型プロパティがありisBeingDismissedます。

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if isBeingDismissed {
        // TODO: Do your stuff here.
    }
}

3
最も簡単なベストアンサーは、ほとんどの問題に正しく対処し、追加の実装を必要としません。
reojased

とペアリングしないと正しく機能しませんviewDidAppear
ドミトリー

9
iOS13モーダルプレゼンテーションでは、ユーザーがコントローラーをドラッグして却下し始めたときにこれが当てはまりますが、却下を完了しないことを選択できます。
ESTEL

44

ブロックプロパティを使用する

VC2で宣言する

var onDoneBlock : ((Bool) -> Void)?

VC1でのセットアップ

VC2.onDoneBlock = { result in
                // Do something
            }

解雇しようとしているときにVC2に電話する

onDoneBlock!(true)

@ Bryce64動作していません。コードがonDoneBlockに送られる時点で、「スレッド1:致命的なエラー:オプションの値のラップ解除中に予期せずnilが見つかりました」というメッセージが表示されました!(true)
Lucas

@ LucasVC1で正しく宣言しなかったようですね。「!」正しく設定されていない場合は、アンラップを強制してエラーを強制します。
brycejl 2018

1
1つのViewControllerのみが提示されていると想定します。あなたはナビゲーションスタックにいる可能性があります神はどこを知っていますか。
リープロバート2018

@LeeProbertその通り。スタックの横に約10個の可能な子コントローラーを備えたナビゲーションコントローラーが提示されており、それらのほぼすべてが解雇をトリガーできます...この状況では、完了ブロックをそのような10個のコントローラーすべてに渡す必要があります
Igor Vasilev

13

提示されたViewController提示されたViewControllerの両方が、提示さdismissViewController:animated:れたViewControllerを閉じるために呼び出すことができます。

前者のオプションは、(おそらく)設計上「正しい」オプションです。同じ「親」ビューコントローラーが、モーダル(「子」)ビューコントローラーの表示と非表示の両方を担当します。

ただし、後者の方が便利です。通常、「閉じる」ボタンは表示されたビューコントローラのビューにアタッチされており、ビューコントローラがアクションターゲットとして設定されています。

前者のアプローチを採用している場合は、現在のView Controllerで、却下が発生するコード行をすでに知っています。コードを実行した直後dismissViewControllerAnimated:completion:、または完了ブロック内で実行します。

後者のアプローチを採用している場合(提示されたビューコントローラーはそれ自体を却下します)、dismissViewControllerAnimated:completion:提示されたビューコントローラーから呼び出すと、UIKitが提示されたビューコントローラーでそのメソッドを呼び出すことに注意してください。

討論

提示するビューコントローラは、提示したビューコントローラを閉じる責任があります。提示されたViewController自体でこのメソッドを呼び出すと、UIKitは提示されたViewControllerに却下を処理するように要求します。

ソース:UIViewControllerクラスリファレンス

だから、インターセプトこのようなイベントにするために、あなたはでそのメソッドをオーバーライドする可能性が提示ビューコントローラ:

override func dismiss(animated flag: Bool,
                         completion: (() -> Void)?) {
    super.dismiss(animated: flag, completion: completion)

    // Your custom code here...
}

1
問題ない。しかし、期待どおりに機能しないことが判明しました。ありがたいことに、@ RoryMcKinnelの回答はより多くのオプションを提供しているようです。
ニコラスミアリ2015年

このアプローチは、ベースビューコントローラーからビューコントローラーをサブクラス化するのに十分一般的ですが、その中でdismissViewControllerAnimatedをオーバーライドします。あなたは、ナビゲーションビューコントローラのビューのcontrolerで包むしようとした場合しかし、それは失敗します
hariszaman

4
ユーザーが上からスワイプしてモーダルビューコントローラーを閉じるときは呼び出されません。
ドミトリー

3
extension Foo: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        //call whatever you want
    }
}

vc.presentationController?.delegate = foo

1
iOS 13.0+のみ
ゴンド

2

unwind segueを使用してこのタスクを実行でき、dismissModalViewControllerを使用する必要はありません。VC1でアンワインドセグエメソッドを定義します。

アンワインドセグエの作成方法については、https: //stackoverflow.com/a/15839298/5647055のこのリンクを参照してください

アンワインドセグエが設定されていると仮定すると、「キャンセル」ボタンに定義されたアクションメソッドで、次のようにセグエを実行できます。

[self performSegueWithIdentifier:@"YourUnwindSegueName" sender:nil];

これで、VC2の[キャンセル]ボタンを押すたびに、VC1が閉じられ、VC1が表示されます。また、VC1で定義したunwindメソッドも呼び出します。これで、提示されたViewControllerがいつ閉じられるかがわかります。


2

以下を使用して、ViewControllerが「完了」したことをコーディネーターに通知します。これはAVPlayerViewControllertvOSアプリケーションのサブクラスで使用され、playerVC解雇の移行が完了した後に呼び出されます。

class PlayerViewController: AVPlayerViewController {
  var onDismissal: (() -> Void)?

  override func beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) {
    super.beginAppearanceTransition(isAppearing, animated: animated)
    transitionCoordinator?.animate(alongsideTransition: nil,
      completion: { [weak self] _ in
         if !isAppearing {
            self?.onDismissal?()
        }
    })
  }
}

AVPLayerViewControllerから継承しないでください。Appleのドキュメントによると、「AVPlayerViewControllerのサブクラス化とそのメソッドのオーバーライドはサポートされておらず、未定義の動作が発生します。」
ネルー

2

willMove(toParent: UIViewController?)次のように使用することは私にとってはうまくいくようでした。(iOS12でテスト済み)。

override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent);

    if parent == nil
    {
        // View controller is being removed.
        // Perform onDismiss action
    }
}

1

@ user523234-「しかし、VC1のdismissViewControllerAnimatedは呼び出されませんでした。」

VC1が実際にプレゼンテーションを行うとは限りません。たとえば、ルートビューコントローラーであるVC0である可能性があります。関連する3つのビューコントローラがあります。

  • sourceViewController
  • presentingViewController
  • presentViewController

あなたの例でVC1 = sourceViewControllerは、、VC2 = presentedViewController?? = presentingViewController-多分VC1、そうでないかもしれません。

ただし、VC2を閉じるときは、VC1.animationControllerForDismissedControllerが呼び出されることを常に信頼できます(デリゲートメソッドを実装している場合)。そのメソッドでは、VC1で必要なことを実行できます。


1

この問題を扱っているときにこの投稿を何度も見たので、私は最終的に可能な答えにいくつかの光を当てるかもしれないと思いました。

ユーザーが開始したアクション(画面上のジェスチャーなど)がUIActionControllerの却下に関与したかどうかを知る必要がある場合、サブクラスや拡張機能などをコードに作成することに時間をたくない場合は、別の方法があります。

それは、結局のところpopoverPresentationControllerののプロパティUIActionController(その旨のか、むしろ、どんなのUIViewController)を、持っているデリゲートあなたがタイプのものであり、あなたのコードにいつでも設定できUIPopoverPresentationControllerDelegateをし、次のメソッドがあります。

アクションコントローラーからデリゲートを割り当て、デリゲートクラス(ビュー、ビューコントローラーなど)に選択したメソッドを実装して、出来上がり!

お役に立てれば。


そして、それらはiOS 13以降非推奨になっています
。Doh


0
  1. 1つのクラスファイル(.h / .m)を作成し、次の名前を付けます:DismissSegue
  2. のサブクラスを選択:UIStoryboardSegue

  3. DismissSegue.mファイルに移動し、次のコードを書き留めます。

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  4. ストーリーボードを開き、Ctrlキーを押しながらキャンセルボタンからVC1にドラッグし、[アクションセグエ]を[却下]として選択すると、完了です。


0

閉じられているViewControllerをオーバーライドする場合:

override func removeFromParentViewController() {
    super.removeFromParentViewController()
    // your code here
}

少なくともこれは私にとってはうまくいきました。


@JohnScaloは正しくありません。かなりの数の「ネイティブ」ビューコントローラー階層が、子/親プリミティブを使用して実装されています。
mxcl 2018年



0

viewWillDisappear提示されたViewControllerのオーバーライド機能。

override func viewWillDisappear(_ animated: Bool) {
    //Your code here
}

とペアリングしないと正しく機能しませんviewDidAppear
ドミトリー

1
必ずsuper.viewWillDisappear–
Dale

0

すでに述べたように、解決策は使用することです override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)です。

なぜoverride func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)常に機能するとは限らないのか疑問に思っている人は、通話がUINavigationController管理されている場合、通話が傍受されていることに気付くかもしれません。私は役立つはずのサブクラスを書きました:

class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }


0

ビューコントローラの却下を処理する場合は、以下のコードを使用する必要があります。

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (self.isBeingDismissed && self.completion != NULL) {
        self.completion();
    }
}

残念ながら、オーバーライドされたメソッドで完了を呼び出すことはできません(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;。このメソッドは、このViewControllerのdismissメソッドを呼び出した場合にのみ呼び出されるためです。


ただしviewWillDisappear、とペアリングしないと正しく機能しませんviewDidAppear
ドミトリー

viewWillDisappearは、VCが完全にカバーされたときに呼び出されます(モーダルなど)。あなたは却下されていない可能性があります
ルー・フランコ

0

ViewControllerにdeinitを使用しました

deinit {
    dataSource.stopUpdates()
}

デイニシャライザーは、クラスインスタンスの割り当てが解除される直前に呼び出されます。

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