83

複数のView Controllerの表示と非表示に関する問題を解決する方法について、過去1週間頭を悩ませています。サンプル プロジェクトを作成し、プロジェクトからコードを直接貼り付けました。対応する .xib ファイルを含む 3 つのビュー コントローラーがあります。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. 

メインのView Controllerに戻りたいと同時に、VC1をメモリから完全に削除する必要があります。VC1 は、メイン コントローラーの VC1 ボタンをクリックしたときにのみ表示されます。

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

4

6 に答える 6

190

この行:

[self dismissViewControllerAnimated:YES completion:nil];

はそれ自体にメッセージを送信しているのではなく、実際には提示元の VC にメッセージを送信して、却下を行うように求めています。VC を提示すると、提示する VC と提示される VC の間に関係が作成されます。したがって、提示中の VC を破棄しないでください (提示された VC は、その却下メッセージを送り返すことはできません…)。あなたは本当にそれを考慮していないので、アプリを混乱した状態のままにしています. 私の回答Dismissing a Presented View Controller を参照してください。 このメソッドはより明確に書かれていることをお勧めします。

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

あなたの場合、すべての制御が で行われていることを確認する必要がありますmainVC 。デリゲートを使用して ViewController1 から MainViewController に正しいメッセージを送り返す必要があります。これにより、mainVC は VC1 を閉じてから VC2 を提示できます。

VC2 VC1では、.h ファイルの @interface の上にプロトコルを追加します。

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

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

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

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

- (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 のView Controller Programming Guideには、次のように書かれています

提示されたView Controllerを閉じる

提示されたビュー コントローラーを閉じるときが来たら、推奨される方法は、提示しているビュー コントローラーにそれを閉じさせることです。つまり、可能な限り、View Controller を提示したのと同じ View Controller が、それを破棄する責任も負う必要があります。提示されたView Controllerを破棄する必要があることを提示側のView Controllerに通知する方法はいくつかありますが、推奨される方法は委譲です。詳細については、「委任を使用して他のコントローラーと通信する」を参照してください。</p>

何を達成したいのか、どのようにそれを実現しようとしているのかをよく考えてみると、NavigationController を使用したくない場合、MainViewController にメッセージを送信してすべての作業を行うことが唯一の論理的な方法であることがわかります。NavControllerを使用する場合、実際には、明示的でなくても、すべての作業を navController に「委譲」していることになります。VC ナビゲーションで何が起こっているかを中心に追跡するオブジェクトが必要であり、何をするにしても、それと通信する何らかの方法が必要です。

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

これは、デリゲートの手間をかけずに機能すると想像できるものです...

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

}

プレゼンティング コントローラーに私たちを却下するように要求した後、presentingViewController 内のメソッドを呼び出して VC2 を呼び出す完了ブロックがあります。代理人は必要ありません。(ブロックの大きなセールス ポイントは、これらの状況でデリゲートの必要性が減ることです)。ただし、この場合、邪魔になることがいくつかあります...

  • VC1では、mainVC がメソッドを実装していることを知りませんpresent2。デバッグが困難なエラーやクラッシュが発生する可能性があります。デリゲートは、これを回避するのに役立ちます。
  • VC1 が閉じられると、完了ブロックを実行するのは実際にはありません... それともそうですか? self.presentingViewController はもう意味がありますか? あなたは知りません (私も知りません)...デリゲートがあれば、この不確実性はありません。
  • このメソッドを実行しようとすると、警告やエラーなしでハングします。

ですから、時間をかけて委任について学んでください。

update2

あなたのコメントでは、VC2 の却下ボタン ハンドラーでこれを使用して、それを機能させることができました。

 [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!

VC2 が提示されたVCである提示 VC を却下しようとしているため、ロジックは崩壊します。2 番目のメッセージは実際には実行されません。おそらく何かが起こるのでしょうが、削除したと思っていたオブジェクトへの 2 つのポインタが残っています。(編集 - これを確認しましたが、それほど悪くはありません。mainVC に戻ると、両方のオブジェクトが消えます)

これはかなり長ったらしい言い方です。デリゲートを使用してください。それが役立つ場合は、ここでパターンの簡単な説明をもう 1 つ作成
しました。コンストラクターでコントローラーを渡すことは常に悪い習慣ですか?

update 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 経由で到着したことを知る必要があるため、依然として高度に結合されていますが、明示的な委譲を少し行わなくても得られる最高のものです。

于 2013-02-16T12:49:59.263 に答える
12

上記のファウンドリーの説明とAppleのドキュメントを描いたSwiftの例:

  1. 上記のApple のドキュメントとファウンドリーの説明 (いくつかのエラーを修正) に基づいて、デリゲート デザイン パターンを使用した現在の ViewController バージョン:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func dismissViewController1AndPresentViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
        self.presentViewController(vc1, animated: true, completion: nil)
    }

    func dismissViewController1AndPresentViewController2() {
        self.dismissViewControllerAnimated(false, completion: { () -> Void in
            let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
            self.presentViewController(vc2, animated: true, completion: nil)
        })
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.dismissViewController1AndPresentViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}
  1. 上記のファウンドリの説明に基づいて (いくつかのエラーを修正)、デリゲート デザイン パターンを使用した pushViewController バージョン:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func popViewController1AndPushViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        self.navigationController?.pushViewController(vc1, animated: true)
    }

    func popViewController1AndPushViewController2() {
        self.navigationController?.popViewControllerAnimated(false)
        let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
        self.navigationController?.pushViewController(vc2, animated: true)
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.popViewController1AndPushViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}
于 2014-12-12T01:21:30.130 に答える
10

iOS モーダル ビュー コントローラーに関するいくつかのコア コンセプトを誤解していると思います。VC1 を閉じると、VC1 によって提示されたビュー コントローラーもすべて閉じられます。Apple は、モーダル ビュー コントローラーがスタック方式でフローすることを意図していました。この場合、VC2 は VC1 によって提示されます。VC1 から VC2 を提示するとすぐに VC1 を却下するので、完全に混乱します。目的を達成するには、buttonPressedFromVC1 は、VC1 が終了した直後に mainVC に VC2 を提示する必要があります。そして、これは代表なしで達成できると思います。線に沿った何か:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

self.presentingViewController は他の変数に格納されていることに注意してください。これは、vc1 がそれ​​自体を閉じた後は、それを参照してはならないためです。

于 2014-02-25T14:59:40.477 に答える
1

提示時に UINavigationController を使用して問題を解決しました。MainVC で、VC1 を提示する場合

let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)

VC1 で、VC2 の表示と VC1 の非表示を同時に (1 つのアニメーションのみ) したい場合は、次の方法でプッシュ アニメーションを使用できます。

let vc2 = VC2()
self.navigationController?.setViewControllers([vc2], animated: true)

VC2 では、View Controller を閉じると、通常どおり次を使用できます。

self.dismiss(animated: true, completion: nil)
于 2017-11-02T17:21:25.900 に答える