1. ホーム
  2. ios

[解決済み] モーダルビューコントローラ - 表示と終了の方法

2023-06-14 04:53:13

質問

複数のビューコントローラーの表示・非表示の問題を解決する方法について、ここ1週間ほど頭を抱えています。サンプルプロジェクトを作成し、そのプロジェクトから直接コードを貼り付けています。3つのビューコントローラとそれに対応する.xibファイルを持っています。MainViewController、VC1、VC2です。メインビューコントローラに2つのボタンがあります。

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

これは問題なくVC1を開きます。VC1 では、VC2 を開くと同時に VC1 を終了させる別のボタンがあります。

- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
    VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
    [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc2 animated:YES completion:nil];
    [self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!


- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1. 

メインビューコントローラに戻したいのですが、同時にVC1が永久にメモリから削除されているはずです。VC1 は、私がメイン コントローラーの VC1 ボタンをクリックしたときだけ表示されるはずです。

メイン ビュー コントローラーの他のボタンも、VC1 をバイパスして直接 VC2 を表示できるようにし、VC2 でボタンがクリックされたときにメイン コントローラーに戻るようにします。長い実行コード、ループ、タイマーはありません。ただ、ビューコントローラを呼び出すだけです。

どのように解決するのですか?

この行です。

[self dismissViewControllerAnimated:YES completion:nil];

は自分自身にメッセージを送っているのではなく、実際にはプレゼンしているVCにメッセージを送り、ディスティングをするように頼んでいるのです。VCを提示するとき、提示するVCと提示されるVCの間に関係が生まれます。ですから、提示中のVCを破壊してはいけません(提示されたVCはdissueメッセージを送り返すことができないので...)。それを考慮に入れていないため、アプリを混乱した状態にしています。私の回答を参照してください。 提示されたビューコントローラを却下する で、この方法をより明確に書くことを推奨しています。

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

あなたの場合、すべての制御が mainVC . ViewController1からMainViewControllerに正しいメッセージを送り返すためにデリゲートを使用する必要があり、mainVCがVC1を解除してからVC2を提示できるようにします。

VC2 VC1 では、@interface の上にある .h ファイルにプロトコルを追加してください。

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

で、同じファイルの下の方の @interface セクションで、デリゲートポインタを保持するプロパティを宣言しています。

@property (nonatomic,weak) id <ViewController1Protocol> delegate;

VC1 .mファイルでは、dismissボタンメソッドはデリゲートメソッドを呼び出す必要があります。

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}

今度はmainVCで、VC1作成時にVC1のデリゲートとして設定します。

- (IBAction)present1:(id)sender {
    ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    vc.delegate = self;
    [self present:vc];
}

で、デリゲートメソッドを実装します。

- (void)dismissAndPresent2 {
    [self dismissViewControllerAnimated:NO completion:^{
        [self present2:nil];
    }];
}

present2: は、あなたの VC2Pressed: ボタン IBAction メソッドと同じメソッドにすることができます。VC1が完全に解除されるまでVC2が表示されないようにするために、補完ブロックから呼び出されることに注意してください。

VC1->VCMain->VC2と移動しているので、おそらくトランジションの1つだけをアニメーションさせたいのでしょう。

更新

コメントの中で、一見単純に見えることを実現するために必要な複雑さに驚きを表現していますね。このデリゲーション パターンは、Objective-C と Cocoa の多くの中心的なものであり、この例は最もシンプルなので、これに慣れるために本当に努力する必要があると断言します。

Apple の ビュー コントローラー プログラミング ガイド では というのは :

<ブロッククオート

提示されたビューコントローラを解除する

提示されたビューコントローラを終了させる場合、提示したビューコントローラに終了させることが望ましい方法です。言い換えれば、可能な限り、ビューコントローラを提示した同じビューコントローラが、それを終了させる責任を負うべきです。提示されたビューコントローラを解除することを提示するビューコントローラに通知するためのいくつかのテクニックがありますが、好ましいテクニックは委任です。詳細については、 "他のコントローラと通信するために委任を使用する "を参照してください。

実現したいことと、その方法についてよく考えてみると、すべての作業を行うためにMainViewControllerにメッセージを送ることが、NavigationControllerを使いたくないということを考えると唯一の論理的な方法であることに気づくでしょう。もし する を使うということは、明示的でなくとも事実上、すべての作業をnavControllerに「委任」していることになる。そのためには いくつかの オブジェクトが必要で、それはVCナビゲーションで何が起こっているかを一元的に追跡するもので、そのためには いくつかの と通信する方法が必要です。

実際には、Appleのアドバイスは少し極端です...通常のケースでは、専用のデリゲートとメソッドを作る必要はなく、以下のものに頼ることができます。 [self presentingViewController] dismissViewControllerAnimated: - に頼ることができます。あなたのように、解任がリモートオブジェクトに他の影響を与えたい場合こそ、注意が必要です。

以下は、あなたができることです。 を想像してみてください。 を、デリゲートに煩わされることなく動作させることができます...

- (IBAction)dismiss:(id)sender {
    [[self presentingViewController] dismissViewControllerAnimated:YES 
                                                        completion:^{
        [self.presentingViewController performSelector:@selector(presentVC2:) 
                                            withObject:nil];
    }];

}

PresentingControllerに解散を依頼した後、VC2を呼び出すためにpresentingViewControllerのメソッドを呼び出す完了ブロックを用意しています。デリゲートは必要ありません。(ブロックの大きなセールスポイントは、このような状況でデリゲートの必要性を減らすことができることです)。しかし、この場合、いくつかのことが邪魔になります...。

  • VC1 では を知っている を実装していることを知りません。 present2 - を実装していると、デバッグが困難なエラーやクラッシュが発生する可能性があります。デリゲートはこれを回避するのに役立ちます。
  • VC1は一度退場させられたら、補完ブロックを実行するために存在するわけではない...ということでしょうか?self.presentingViewControllerはもう何の意味もないのでしょうか?あなたは知らない(私も知らない)...デリゲートでは、この不確実性はありません。
  • このメソッドを実行しようとすると、警告もエラーもなく、ただハングアップするだけです。

だからお願い...時間をかけてデリゲーションを学んでください!

更新2

コメントでは、VC2のdissueボタンハンドラでこれを使うことでなんとか動作させることができたようですね。

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

これは確かにずっとシンプルですが、いくつかの問題を残しています。

タイトなカップリング

viewControllerの構造をハードワイヤリングで結合しています。例えば、mainVCの前に新しいviewControllerを挿入すると、必要な動作が壊れます(前のものに移動してしまいます)。VC1では、VC2も#importする必要があった。したがって、かなり多くの相互依存関係があり、OOP/MVCの目的を壊しています。

デリゲートを使用すると、VC1もVC2もmainVCやその前身について何も知る必要がないので、すべてを疎結合にし、モジュール化することができます。

メモリ



VC1 は消えていません。あなたはまだそれに対する 2 つのポインターを保持しています。

  • mainVC の presentedViewController プロパティ
  • VC2 の presentingViewController プロパティ

ログを取ることでこれをテストできますし、VC2からこれを実行することでもテストできます。

[self dismissViewControllerAnimated:YES completion:nil]; 

まだ動作し、VC1 に戻ることができます。

メモリリークのような気がするのですが。

その手がかりは、ここで出ている警告にあります。

[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
 // Attempt to dismiss from view controller <VC1: 0x715e460>
 // while a presentation or dismiss is in progress!

プレゼンVCを否定しようとしているのだから、論理が破綻している。 うち VC2 は提示された VC です。2 番目のメッセージは実際には実行されません。まあ、おそらく何らかのことが起こるのでしょうが、取り除いたと思っていたオブジェクトへの 2 つのポインタが残されたままです。( edit - これをチェックしたところ、それほど悪くはありませんでした。 )

これはかなり長い言い回しですが、デリゲートを使ってください。もしお役に立てるのであれば、このパターンについて別の簡単な説明をここに作りました。

コンストラクタでコントローラを渡すのは常に悪い習慣なのでしょうか?

アップデート3

本当にデリゲートを避けたいなら、これが一番いい方法かもしれません。

VC1では

[self presentViewController:VC2
                   animated:YES
                 completion:nil];

しかし しない 私たちが確認したように、それは実際には起こらないのです。

VC2にて。

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];

私たちはVC1を破棄していない(と知っている)ので、過去にさかのぼって を通して VC1 まで MainVCへ。MainVCはVC1を退場させる。VC1が消えたので、VC2も一緒に提示されるので、きれいな状態でMainVCに戻る。

VC1はVC2について知る必要があり、VC2はMainVC->VC1経由で到達したことを知る必要があるので、まだ高度に結合されていますが、これは少し明示的な委任がなければ得られる最高のものです。