web-dev-qa-db-ja.com

モーダルView Controller-表示および非表示の方法

ここ1週間、複数のView Controllerの表示と非表示に関する問題を解決する方法について頭を痛めています。サンプルプロジェクトを作成し、プロジェクトから直接コードを貼り付けました。対応する.xibファイルを持つ3つのView Controllerがあります。 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のボタンがクリックされるとメインコントローラーに戻るはずです。長時間実行されるコード、ループ、タイマーはありません。コントローラーを表示するための単なる骨の呼び出し。

80
Hema

この行:

[self dismissViewControllerAnimated:YES completion:nil];

それ自体にメッセージを送信しているのではなく、実際に提示しているVCにメッセージを送信して、却下するように依頼しています。 VCを提示するとき、提示VCと提示されたものとの関係を作成します。そのため、提示中のVCを破棄することはできません(提示されたVCはその却下メッセージを返信できません...)。あなたは本当にそれを考慮していないので、あなたは混乱した状態でアプリを残しています。私の答えを参照してください 提示されたView Controllerを削除する この方法をお勧めします:

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

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

に 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: button IBActionメソッドと同じメソッドにすることができます。 VC1が完全に破棄されるまでVC2が表示されないようにするために、完了ブロックから呼び出されることに注意してください。

VC1-> VCMain-> VC2から移動しているので、おそらくトランジションの1つだけをアニメーション化する必要があります。

update

あなたのコメントでは、一見単純なことを達成するために必要な複雑さに驚きを表明しています。この委任パターンはObjective-CとCocoaの大部分の中心であり、この例はあなたが得ることができる最も単純なものであるため、本当にそれに慣れる努力をする必要があります。

AppleのView Controllerプログラミングガイドには これは言う :があります。

提示されたView Controllerの削除

提示されたView Controllerを却下するときが来るとき、望ましいアプローチは提示View Controllerにそれを却下させることです。つまり、可能な場合は、View Controllerを表示した同じView Controllerも、それを破棄する責任を負う必要があります。表示中のView Controllerを削除する必要があることを表示中のView Controllerに通知する方法はいくつかありますが、推奨される方法は委任です。詳細については、「委任を使用して他のコントローラーと通信する」を参照してください。

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

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

ここにあなたができる何かがありますimagineすべてのデリゲートの手間をかけずに動作する...

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

}

提示するコントローラーに私たちを解任するように依頼した後、VC2を呼び出すためにpresentingViewControllerのメソッドを呼び出す完了ブロックがあります。代理人は必要ありません。 (ブロックの大きなセールスポイントは、これらの状況でデリゲートの必要性を減らすことです)。ただし、この場合、いくつかの障害があります...

  • vC1では、mainVCがメソッドpresent2を実装していることをknowしません-デバッグが困難なエラーまたはクラッシュを引き起こす可能性があります。デリゲートは、これを回避するのに役立ちます。
  • 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!

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

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

update 3
代理人を本当に避けたい場合、これが最善の方法です。

VC1の場合:

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

しかし、do n't何も却下します...確認したように、それは実際には起こりません。

VC2の場合:

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

VC1を却下していない(知っている)ので、戻ることができますthrough VC1 to MainVC。 MainVCはVC1を閉じます。 VC1がなくなったため、VC2が一緒に表示されるため、クリーンな状態でMainVCに戻ります。

VC1はVC2について知る必要があり、VC2はそれがMainVC-> VC1を介して到達したことを知る必要があるため、依然として高度に結合されていますが、明示的な委任を少しせずに取得するのが最善です。

186
foundry

Swiftの例では、上記のファウンドリの説明とAppleのドキュメントを示しています。

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

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 {

}
9
King-Wizard

IOSのモーダルビューコントローラに関するいくつかのコアコンセプトを誤解していると思います。 VC1を閉じると、VC1によって表示されたView Controllerもすべて閉じられます。 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が終了した後は、それを参照しないでください。

9
Radu Simionescu

Radu Simionescu-すばらしい仕事です!以下のSwift恋人のためのソリューション:

@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) {
    let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it
    var presentingVC = self.presentingViewController
    self.dismissViewControllerAnimated(false, completion: { () -> Void   in
        presentingVC!.presentViewController(secondViewController, animated: true, completion: nil)
    })
}
4
chrisco

これが欲しかった:

MapVCはフルスクリーンの地図です。

ボタンを押すと、マップの上に(フルスクリーンではなく)PopupVCが開きます。

PopupVCでボタンを押すと、MapVCに戻り、viewDidAppearを実行したい。

これは私がしました:

MapVC.m:ボタンアクション、セグエプログラム、およびデリゲートの設定

- (void) buttonMapAction{
   PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"];
   popvc.delegate = self;
   [self presentViewController:popvc animated:YES completion:nil];
}

- (void)dismissAndPresentMap {
  [self dismissViewControllerAnimated:NO completion:^{
    NSLog(@"dismissAndPresentMap");
    //When returns of the other view I call viewDidAppear but you can call to other functions
    [self viewDidAppear:YES];
  }];
}

PopupVC.h:@interfaceの前に、プロトコルを追加します

@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end

@interfaceの後、新しいプロパティ

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

PopupVC.m:

- (void) buttonPopupAction{
  //jump to dismissAndPresentMap on Map view
  [self.delegate dismissAndPresentMap];
}
0
Mer

プレゼンテーションの際に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)
0
Duong Ngo