web-dev-qa-db-ja.com

iPhone-複数のViewControllerを閉じる

長いView Controller階層があります。

最初のView Controllerでこのコードを使用します:

SecondViewController *svc = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
[self presentModalViewController:svc animated:YES];    
[svc release];

2番目のView Controllerでは、次のコードを使用します。

ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self presentModalViewController:tvc animated:YES];    
[tvc release];

等々。

そのため、多くのView Controllerがあり、最初のView Controllerに戻る必要がある瞬間があります。一度に1ステップ戻った場合、すべてのView Controllerで次のコードを使用します。

[self dismissModalViewControllerAnimated:YES];

たとえば、6番目のView Controllerから最初のView Controllerに直接戻りたい場合、すべてのControllerを一度に閉じるにはどうすればよいですか?

ありがとう

45
Oscar Peli

私は解決策を見つけました。

もちろん、最も明白な場所で解決策を見つけることができるので、dismissModalViewControllerAnimatedメソッドのUIViewControllerリファレンスを読むと...

複数のモーダルビューコントローラーを連続して提示して、モーダルビューコントローラーのスタックを構築する場合、スタックの下位のビューコントローラーでこのメソッドを呼び出すと、その直接の子ビューコントローラーとスタック上のその子より上のすべてのビューコントローラーが破棄されます。これが発生すると、一番上のビューのみがアニメーション形式で閉じられます。中間のView Controllerはスタックから単に削除されます。一番上のビューは、モーダル遷移スタイルを使用して閉じられます。これは、スタック内の下位の他のView Controllerで使用されるスタイルとは異なる場合があります。

そのため、ターゲットビューでdismissModalViewControllerAnimatedを呼び出すだけで十分です。次のコードを使用しました。

[[[[[self parentViewController] parentViewController] parentViewController] parentViewController] dismissModalViewControllerAnimated:YES];

家に帰ります.

24
Oscar Peli

アニメーションコンテキストを間違わずに全画面表示を解除するiOS 8以降の汎用メソッド。 Objective-CおよびSwiftでは

Objective-C

- (void)dismissModalStackAnimated:(bool)animated completion:(void (^)(void))completion {
    UIView *fullscreenSnapshot = [[UIApplication sharedApplication].delegate.window snapshotViewAfterScreenUpdates:false];
    [self.presentedViewController.view insertSubview:fullscreenSnapshot atIndex:NSIntegerMax];
    [self dismissViewControllerAnimated:animated completion:completion];
}

Swift

func dismissModalStack(animated: Bool, completion: (() -> Void)?) {
    if let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false) {
        presentedViewController?.view.addSubview(fullscreenSnapshot)
    }
    if !isBeingDismissed {
        dismiss(animated: animated, completion: completion)
    }
}

tl; dr

他のソリューションの何が問題になっていますか?

多くの解決策がありますが、それらのいずれも間違った却下コンテキストとはみなされません:

例えばルートA->プレゼンツB->プレゼンツCで、CからAに却下したい場合は、dismissViewControllerAnimatedrootViewControllerを呼び出して公式にできます。

 [[UIApplication sharedApplication].delegate.window.rootViewController dismissModalStackAnimated:true completion:nil];

ただしこのルートでのCからの呼び出し解除は、誤った遷移を伴う正しい動作につながります(CからAの代わりにBからAが表示されます)。


so

普遍的な却下メソッドを作成しました。このメソッド現在のフルスクリーンスナップショットを取得し、受信者の提示されたView Controller上に配置し、それをすべて却下します(例:デフォルトの呼び出しをCから呼び出しますが、Bは本当に却下されているように見える)

15
Jakub Truhlář

最初のView Controllerがルート/初期View Controller(ストーリーボードで初期View Controllerとして指定したもの)でもあるとします。要求をリッスンするように設定して、表示されているすべてのView Controllerを削除できます。

firstViewController:

- (void)viewDidLoad {
    [super viewDidLoad];

    // listen to any requests to dismiss all stacked view controllers
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissAllViewControllers:) name:@"YourDismissAllViewControllersIdentifier" object:nil];

    // the remainder of viewDidLoad ...
}

// this method gets called whenever a notification is posted to dismiss all view controllers
- (void)dismissAllViewControllers:(NSNotification *)notification {
    // dismiss all view controllers in the navigation stack
    [self dismissViewControllerAnimated:YES completion:^{}];
}

そして、ナビゲーションスタックの一番上に戻るべきであると決定するナビゲーションスタックの下にある他のView Controllerで:

[[NSNotificationCenter defaultCenter] postNotificationName:@"YourDismissAllViewControllersIdentifier" object:self];

これにより、モーダルで表示されたすべてのView Controllerがアニメーションで閉じられ、ルートView Controllerのみが残ります。これは、最初のView ControllerがUINavigationControllerであり、最初のView ControllerがルートView Controllerとして設定されている場合にも機能します。

ボーナスのヒント:通知名が同じであることが重要です。入力エラーによる誤解を避けるために、アプリ内のどこかにこの通知名を変数として定義することをお勧めします。

14
Thomas Verbeek
[[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];

却下するすべてのコントローラーにデリゲートを実装することもできます

5

すべてがモデルビューコントローラーを使用している場合は、事前設定されたすべてのビューコントローラーを閉じるための通知を使用できます。

1.このようにRootViewControllerに通知を登録する

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(dismissModelViewController)
                                             name:dismissModelViewController
                                           object:nil];

2. rootviewControllerでdismissModelViewController関数を実装する

- (void)dismissModelViewController
{
    While (![self.navigationController.visibleViewController isMemberOfClass:[RootviewController class]])
    {
        [self.navigationController.visibleViewController dismissViewControllerAnimated:NO completion:nil];
    }
}

3.ボタンイベントを閉じるか閉じるたびに通知が投稿されます。

   [[NSNotificationCenter defaultCenter] postNotificationName:dismissModelViewController object:self];
3
Chathurka

Swiftの場合:

self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
3
richyrich24

ほとんどのソリューションの問題は、表示されたviewControllerのスタックを閉じると、スタック内の最初に表示されたviewControllerが表示されている間、ユーザーに表示されることです。 Jakubの優れたソリューションはそれを解決します。これが彼の答えに基づいた拡張です。

extension UIViewController {

    func dismissAll(animated: Bool, completion: (() -> Void)? = nil) {
        if let optionalWindow = UIApplication.shared.delegate?.window, let window = optionalWindow, let rootViewController = window.rootViewController, let presentedViewController = rootViewController.presentedViewController  {
            if let snapshotView = window.snapshotView(afterScreenUpdates: false) {
                presentedViewController.view.addSubview(snapshotView)
                presentedViewController.modalTransitionStyle = .coverVertical
            }
            if !isBeingDismissed {
                rootViewController.dismiss(animated: animated, completion: completion)
            }
        }
    }

}

使用法:ルートに戻して表示するviewControllerからこの拡張関数を呼び出します。

@IBAction func close() {
    dismissAll(animated: true)
}
3
Harris

これを試して..

ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self.view addsubview:tvc];    
[tvc release];
2
Chu Chau

A Swift this コメントに基づいて追加されたバージョン

func dismissModalStack(viewController: UIViewController, animated: Bool, completionBlock: BasicBlock?) {
    if viewController.presentingViewController != nil {
        var vc = viewController.presentingViewController!
        while (vc.presentingViewController != nil) {
            vc = vc.presentingViewController!;
        }
        vc.dismissViewControllerAnimated(animated, completion: nil)

        if let c = completionBlock {
            c()
        }
    }
}

単純な再帰的クローザー:

extension UIViewController {
    final public func dismissEntireStackAndSelf(animate: Bool = true) {
        // Always false on non-calling controller
        presentedViewController?.ip_dismissEntireStackAndSelf(false)
        self.dismissViewControllerAnimated(animate, completion: nil)
    }
}

これにより、すべての子コントローラーが強制的に閉じられ、自己のみがアニメーション化されます。好きなように切り替えることができますが、各コントローラーをアニメーション化すると、それらは1つずつ移動し、遅くなります。

コール

baseController.dismissEntireStackAndSelf()
1
Logan

ルートView Controllerに戻るために、すべてのView Controllerをポップして閉じるために使用するソリューションを次に示します。 UIViewControllerのカテゴリにこれらの2つのメソッドがあります。

+ (UIViewController*)topmostViewController
{
    UIViewController* vc = [[[UIApplication sharedApplication] keyWindow] rootViewController];
    while(vc.presentedViewController) {
        vc = vc.presentedViewController;
    }
    return vc;
}

+ (void)returnToRootViewController
{
    UIViewController* vc = [UIViewController topmostViewController];
    while (vc) {
        if([vc isKindOfClass:[UINavigationController class]]) {
            [(UINavigationController*)vc popToRootViewControllerAnimated:NO];
        }
        if(vc.presentingViewController) {
            [vc dismissViewControllerAnimated:NO completion:^{}];
        }
        vc = vc.presentingViewController;
    }
}

それから私はただ電話する

[UIViewController returnToRootViewController];
1
Nikolay Spassov

上記の回答に基づいたSwift拡張:

extension UIViewController {

    func dismissUntilAnimated<T: UIViewController>(animated: Bool, viewController: T.Type, completion: ((viewController: T) -> Void)?) {
        var vc = presentingViewController!
        while let new = vc.presentingViewController where !(new is T) {
            vc = new
        }
        vc.dismissViewControllerAnimated(animated, completion: {
            completion?(viewController: vc as! T)
        })
    }
}

Swift 3.0バージョン:

extension UIViewController {

    /// Dismiss all modally presented view controllers until a specified view controller is reached. If no view controller is found, this function will do nothing.

    /// - Parameter reached:      The type of the view controller to dismiss until.
    /// - Parameter flag:         Pass `true` to animate the transition.
    /// - Parameter completion:   The block to execute after the view controller is dismissed. This block contains the instance of the `presentingViewController`. You may specify `nil` for this parameter.
    func dismiss<T: UIViewController>(until reached: T.Type, animated flag: Bool, completion: ((T) -> Void)? = nil) {
        guard let presenting = presentingViewController as? T else {
            return presentingViewController?.dismiss(until: reached, animated: flag, completion: completion) ?? ()
        }

        presenting.dismiss(animated: flag) {
            completion?(presenting)
        }
    }
}

ほとんどの場合、モーダルビューコントローラーの表示ビューコントローラーがUITabBarControllerこれをまったく役に立たないことを考えると、これが信じられないほど愚かなロジックであるため、これを作成した理由を完全に忘れてしまいました。実際にベースビューコントローラーインスタンスを取得し、その上でdismissを呼び出す方がはるかに理にかなっています。

1
Mark Bourke
  id vc = [self presentingViewController];
  id lastVC = self;
  while (vc != nil) {
    id tmp = vc;
    vc = [vc presentingViewController];
    lastVC = tmp;
  }
  [lastVC dismissViewControllerAnimated:YES completion:^{
}];
1
Ashish Awaghad

Swift上記の回答に基づく拡張。

そのようなスタックの原理:A-> B-> C-> D

  • Dのスナップショットを撮る
  • このスナップショットをBに追加します
  • アニメーションなしでBから解散
  • 完了したら、Aをアニメーションで閉じます

    extension UIViewController {
    
        func dismissModalStack(animated: Bool, completion: (() -> Void)?) {
            let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false)
            if !isBeingDismissed {
                var rootVc = presentingViewController
                while rootVc?.presentingViewController != nil {
                    rootVc = rootVc?.presentingViewController
                }
                let secondToLastVc = rootVc?.presentedViewController
                if fullscreenSnapshot != nil {
                    secondToLastVc?.view.addSubview(fullscreenSnapshot!)
                }
                secondToLastVc?.dismiss(animated: false, completion: {
                    rootVc?.dismiss(animated: true, completion: completion)
                })
            }
        }
    }
    

シミュレータでは少しちらつきますが、デバイスではちらつきません。

1
squall2022

まず、オスカーペリはコードをありがとう。

最初にnavigationControllerを起動するには、この方法でもう少し動的にすることができます。 (スタック内のViewControllerの数がわからない場合)

NSArray *viewControllers = self.navigationController.viewControllers;
[self.navigationController popToViewController: [viewControllers objectAtIndex:0] animated: YES];
1
Ben Groot

Swift 3.0 +の場合

self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil)

これにより、rootviewcontrollerで表示されているすべてのView Controllerが閉じられます。

1
Bhuvan Bhatt

この一般的なソリューションを使用して、この問題を解決します。

- (UIViewController*)topViewController
{
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }
    return topController;
}


- (void)dismissAllModalController{

    __block UIViewController *topController = [self topViewController];

    while (topController.presentingViewController) {
        [topController dismissViewControllerAnimated:NO completion:^{

        }];
        topController = [self topViewController];
    }
}
1
SachinVsSachin

上部のVCアニメーション化されたものとそうでないものを削除します。

[self dismissModalViewControllerAnimated:NO]; // First
[self dismissModalViewControllerAnimated:NO]; // Second
[self dismissModalViewControllerAnimated:YES]; // Third

編集:1つの方法だけでこれを行いたい場合は、階層をVCの配列に保存し、最後にアニメーション化されたオブジェクトとそれ以外のオブジェクトは破棄します。

0
emenegro

dismiss(animated:completion:) メソッドに関するAppleドキュメント。

セクションDiscussionでは、次のように述べました。

any intermediate view controllers are simply removed from the stack.

複数のView Controllerを連続して提示し、提示されたView Controllerのスタックを構築する場合、スタックの下位のView Controllerでこのメソッドを呼び出すと、その直接の子View Controllerとスタック上のその子より上のすべてのView Controllerが破棄されます。これが発生すると、一番上のビューのみがアニメーション形式で閉じられます。 中間のView Controllerはすべてスタックから削除されます。最上位のビューは、モーダル遷移スタイルを使用して閉じられます。スタックの下位にある他のView Controller。

つまり、View Controllerが次のようにスタックしている場合

Root -> A -> B -> C -> D ... -> Z

Ddismissメソッドを呼び出し、すべてのView ControllerはDを非表示にします。例:(E ... Z)、スタックから削除されます。

0
AechoLiu