popViewControllerの完了ブロック


113

を使用してモーダルビューコントローラーを閉じる場合dismissViewController、完了ブロックを提供するオプションがあります。同様の同等物はありpopViewControllerますか?

完了の引数は非常に便利です。たとえば、モーダルが画面から消えるまでテーブルビューから行を削除するのを保留して、ユーザーに行のアニメーションを表示させることができます。プッシュされたビューコントローラーから戻るときも同じ機会が欲しいです。

アニメーションブロックに配置popViewControllerしてみましたUIViewが、完了ブロックにアクセスできます。ただし、これにより、ポップされるビューに不要な副作用が生じます。

そのような方法が利用できない場合、いくつかの回避策は何ですか?


stackoverflow.com/a/33767837/2774520私はこの方法が最もネイティブな方法だと思います
Oleksii Nezhyborets


3
2018年の場合、これは非常にシンプルで標準的です 。stackoverflow.com
a

回答:


199

2年以上前に回答が受け入れられたことは知っていますが、この回答は不完全です。

すぐに必要なことを行う方法はありません

UINavigationControllerAPIにはこのためのオプションがないため、これは技術的に正しいです。ただし、CoreAnimationフレームワークを使用することにより、基になるアニメーションに完了ブロックを追加することができます。

[CATransaction begin];
[CATransaction setCompletionBlock:^{
    // handle completion here
}];

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

によって使用されるアニメーションがpopViewControllerAnimated:終了するとすぐに、完了ブロックが呼び出されます。この機能はiOS 4以降で利用できます。


5
私はこれをSwiftのUINavigationControllerの拡張機能に入れました:extension UINavigationController { func popViewControllerWithHandler(handler: ()->()) { CATransaction.begin() CATransaction.setCompletionBlock(handler) self.popViewControllerAnimated(true) CATransaction.commit() } }
Arbitur

1
私には機能しないようです。dismissViewControllerでcompletionHandlerを実行すると、それを表示していたビューはビュー階層の一部です。CATransactionで同じことを行うと、ビューがビュー階層の一部ではないという警告が表示されます。
moger777 2015

1
OK、開始ブロックと完了ブロックを逆にすると、あなたの作品のように見えます。反対票については申し訳ありませんが、スタックオーバーフローでは変更できません:(
moger777

7
ええ、これは素晴らしいことのように見えましたが、機能していないようです(少なくともiOS 8では)。完了ブロックはすぐに呼び出されます。コアアニメーションとUIViewスタイルのアニメーションが混在している可能性があります。
stuckj 2015年

5
これは機能しません
durazno

51

iOS9 SWIFTバージョン- (以前のバージョンのためにテストされていなかった)魔法のように動作します。この答えに基づいて

extension UINavigationController {    
    func pushViewController(viewController: UIViewController, animated: Bool, completion: () -> ()) {
        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }

    func popViewController(animated: Bool, completion: () -> ()) {
        popViewControllerAnimated(animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

アニメーション化されていない場合は機能しません。正しく実行するには、次の実行ループで完了を行う必要があります。
rshev 2016

@rshevなぜ次のランループに?
ベンシンクレア

@Andy私がこれを実験して覚えていることから、その時点ではまだ何も伝わっていませんでした。それを試してみて、それがどのように機能するかを聞いてください。
rshev

@rshev私は以前と同じようにそれを持っていると思います、私は再確認する必要があります。現在のテストは問題なく実行されます。
ベンシンクレア

1
@LanceSamaria viewDidDisappearを使用することをお勧めします。navbarが利用可能かどうかを確認します。利用できない場合は、navbarに表示されないため、ポップされました。if(self.navigationController == nil){アクションをトリガーする}
HotJard

32

私は@JorisKluiversの回答Swiftで拡張子付きのバージョンを作成しました。

これは、pushとの両方のアニメーションが完了した後に完了クロージャを呼び出しますpop

extension UINavigationController {
    func popViewControllerWithHandler(completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewControllerAnimated(true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}

私にとって、iOS 8.4では、ObjCで記述されており、ブロックはアニメーションの途中まで起動します。Swift(8.4)で記述した場合、これは本当に適切なタイミングで起動しますか?
ジュリアンF.ウェイナート2015

@Arbitur完了ブロックは、実際に呼び出した後に呼び出されpopViewControllerたりpushViewControllerしていますが、topViewControllerが右その後であるかどうか確認する場合、あなたはそれが同じように、まだ古いものであることがわかるでしょうpoppush起こったことはありません...
ボグダンはRazvan

@BogdanRazvanすぐ後でどうなりますか?アニメーションが完了すると、完了クロージャーが呼び出されますか?
アービトゥール

17

SWIFT 4.1

extension UINavigationController {
func pushToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.pushViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popViewController(animated: animated)
    CATransaction.commit()
}

func popToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popToRootViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToRootViewController(animated: animated)
    CATransaction.commit()
}
}

17

同じ問題がありました。そして、私はそれを複数の場面で使用する必要があったため、完了ブロックのチェーン内で、この汎用ソリューションをUINavigationControllerサブクラスで作成しました。

- (void) navigationController:(UINavigationController *) navigationController didShowViewController:(UIViewController *) viewController animated:(BOOL) animated {
    if (_completion) {
        dispatch_async(dispatch_get_main_queue(),
        ^{
            _completion();
            _completion = nil;
         });
    }
}

- (UIViewController *) popViewControllerAnimated:(BOOL) animated completion:(void (^)()) completion {
    _completion = completion;
    return [super popViewControllerAnimated:animated];
}

想定

@interface NavigationController : UINavigationController <UINavigationControllerDelegate>

そして

@implementation NavigationController {
    void (^_completion)();
}

そして

- (id) initWithRootViewController:(UIViewController *) rootViewController {
    self = [super initWithRootViewController:rootViewController];
    if (self) {
        self.delegate = self;
    }
    return self;
}

1
私はこのソリューションが本当に好きです。カテゴリと関連オブジェクトで試してみます。
spstanley 14

@spstanleyこのポッドを公開する必要があります:)
k06a 2014年


15

すぐに使用したいことを行う方法はありません。つまり、ナビゲーションスタックからビューコントローラーをポップするための完了ブロックを持つメソッドはありません。

私がやろうとしていることは、ロジックをに入れることですviewDidAppear。ビューが画面に表示されたときに呼び出されます。これは、ビューコントローラーが表示されるさまざまなシナリオすべてで呼び出されますが、問題はありません。

または、UINavigationControllerDelegateメソッドnavigationController:didShowViewController:animated:を使用して同様のことを行うことができます。これは、ナビゲーションコントローラーがビューコントローラーのプッシュまたはポップを完了したときに呼び出されます。


私はこれを試みました。「削除された行インデックス」の配列を保存し、ビューが表示されるたびに、削除する必要があるものがあるかどうかを確認していました。すぐに手に負えなくなりましたが、もう一度試してみるかもしれません。なぜAppleはそれを一方の移行に提供し、他方の移行には提供しないのだろうか?
Ben Packard

1
は非常に新しいだけdismissViewControllerです。多分それは来るでしょうpopViewController。レーダーを提出してください:-)。
mattjgalloway 2012年

しかし真剣に、レーダーを提出してください。人々がそれを求めればそれはそれを作る可能性が高いです。
mattjgalloway 2012年

1
それはそれを求めるための正しい場所です。分類を「機能」にするオプションがあります。
mattjgalloway 2012年

3
この答えは完全に正しくはありません。onのように新しいスタイルのブロックを設定することはできません-dismissViewController:animated:completionBlock:が、ナビゲーションコントローラーのデリゲートからアニメーションを取得できます。アニメーションが完了-navigationController:didShowViewController:animated:すると、がデリゲートで呼び出され、そこで必要なことをすべて実行できます。
Jason Coco

13

アニメーションを使用して、または使用せずに適切に作業しますpopToRootViewController

 // updated for Swift 3.0
extension UINavigationController {

  private func doAfterAnimatingTransition(animated: Bool, completion: @escaping (() -> Void)) {
    if let coordinator = transitionCoordinator, animated {
      coordinator.animate(alongsideTransition: nil, completion: { _ in
        completion()
      })
    } else {
      DispatchQueue.main.async {
        completion()
      }
    }
  }

  func pushViewController(viewController: UIViewController, animated: Bool, completion: @escaping (() ->     Void)) {
    pushViewController(viewController, animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popToRootViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popToRootViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }
}

completion()非同期を呼び出す特別な理由はありますか?
リバイアサン

1
コーディネーターを使用したアニメーションがcompletion同じ実行ループで実行されることはありません。これによりcompletion、アニメーション化していないときに同じ実行ループで実行されることはありません。この種の不整合が発生しないようにすることをお勧めします。
rshev

11

@HotJardの答えに基づくと、必要なのは数行のコードだけです。早くて簡単。

Swift 4

_ = self.navigationController?.popViewController(animated: true)
self.navigationController?.transitionCoordinator.animate(alongsideTransition: nil) { _ in
    doWhatIWantAfterContollerHasPopped()
}

6

2018年に...

これがあれば...

    navigationController?.popViewController(animated: false)
    // I want this to happen next, help! ->
    nextStep()

そして補完を追加したい...

    CATransaction.begin()
    navigationController?.popViewController(animated: true)
    CATransaction.setCompletionBlock({ [weak self] in
       self?.nextStep() })
    CATransaction.commit()

とても簡単です。

便利なヒント...

ハンディpopToViewControllerコールも同じです。

典型的なことは、何十億もの画面のオンボーディングスタックがあることです。最後に、「ベース」画面に戻り、最後にアプリを起動します。

「ベース」画面で、「ずっと前に戻る」ために、 popToViewController(self

func onboardingStackFinallyComplete() {
    
    CATransaction.begin()
    navigationController?.popToViewController(self, animated: false)
    CATransaction.setCompletionBlock({ [weak self] in
        guard let self = self else { return }
        .. actually launch the main part of the app
    })
    CATransaction.commit()
}

5

表示されたビューコントローラーでviewDidDisappearメソッドが呼び出された後に完了ブロックが呼び出されるため、ポップされたビューコントローラーのviewDidDisappearメソッドにコードを配置すると、完了ブロックと同じように機能します。


確かに-それ以外の場合は、他の理由でビューが消えているすべてのケースを処理する必要があります。
Ben Packard

1
@BenPackard、はい、そしてあなたが受け入れた答えのviewDidAppearにそれを置くことについても同じことが言えます。
rdelmar 2012年

5

この回答のおかげで、Swift 3の回答:https : //stackoverflow.com/a/28232570/3412567

    //MARK:UINavigationController Extension
extension UINavigationController {
    //Same function as "popViewController", but allow us to know when this function ends
    func popViewControllerWithHandler(completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewController(animated: true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}

4

特定のビューにポップするオプションのviewControllerパラメータを持つSwift 4バージョン。

extension UINavigationController {
    func pushViewController(viewController: UIViewController, animated: 
        Bool, completion: @escaping () -> ()) {

        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
}

func popViewController(viewController: UIViewController? = nil, 
    animated: Bool, completion: @escaping () -> ()) {
        if let viewController = viewController {
            popToViewController(viewController, animated: animated)
        } else {
            popViewController(animated: animated)
        }

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

受け入れられた回答は、私が持っているすべてのエミュレーター/デバイスを使用する私の開発環境で動作するようですが、それでも本番ユーザーからバグが報告されます。これが本番の問題を解決するかどうかはわかりませんが、受け入れられた回答から同じ問題が発生した場合に誰かが試してみることができるように、賛成票を投じます。
Sean

4

この回答に基づいてSwift 4バージョンをクリーンアップしました。

extension UINavigationController {
    func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
        self.pushViewController(viewController, animated: animated)
        self.callCompletion(animated: animated, completion: completion)
    }

    func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController? {
        let viewController = self.popViewController(animated: animated)
        self.callCompletion(animated: animated, completion: completion)
        return viewController
    }

    private func callCompletion(animated: Bool, completion: @escaping () -> Void) {
        if animated, let coordinator = self.transitionCoordinator {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}


2

2020 Swift 5.1ウェイ

このソリューションは、popViewControllerが完全に終了した後に完了が実行されることを保証します。NavigationControllerで別の操作を完了すると、それをテストできます。上記の他のすべてのソリューションでは、UINavigationControllerはまだpopViewController操作でビジーであり、応答しません。

public class NavigationController: UINavigationController, UINavigationControllerDelegate
{
    private var completion: (() -> Void)?

    override init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)
        delegate = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
    {
        if self.completion != nil {
            DispatchQueue.main.async(execute: {
                self.completion?()
                self.completion = nil
            })
        }
    }

    func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController?
    {
        self.completion = completion
        return super.popViewController(animated: animated)
    }
}

1

完全を期すために、Objective-Cカテゴリーを使用できるようにしました。

// UINavigationController+CompletionBlock.h

#import <UIKit/UIKit.h>

@interface UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion;

@end
// UINavigationController+CompletionBlock.m

#import "UINavigationController+CompletionBlock.h"

@implementation UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion {
    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        completion();
    }];

    UIViewController *vc = [self popViewControllerAnimated:animated];

    [CATransaction commit];

    return vc;
}

@end

1

私はこれを正確にブロックを使用して達成しました。フェッチされた結果コントローラーに、モーダルビューによって追加された行を、画面を完全に離れたときにのみ表示して、ユーザーが変更を確認できるようにしたいと考えました。モーダルビューコントローラーの表示を担当するセグエの準備で、モーダルが消えたときに実行するブロックを設定します。モーダルビューコントローラーで、viewDidDissapearをオーバーライドしてからブロックを呼び出します。モーダルが表示されるときに更新を開始し、モーダルが表示されなくなると更新を終了するだけですが、これはNSFetchedResultsControllerを使用しているためですが、ブロック内で好きなように実行できます。

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"addPassword"]){

        UINavigationController* nav = (UINavigationController*)segue.destinationViewController;
        AddPasswordViewController* v = (AddPasswordViewController*)nav.topViewController;

...

        // makes row appear after modal is away.
        [self.tableView beginUpdates];
        [v setViewDidDissapear:^(BOOL animated) {
            [self.tableView endUpdates];
        }];
    }
}

@interface AddPasswordViewController : UITableViewController<UITextFieldDelegate>

...

@property (nonatomic, copy, nullable) void (^viewDidDissapear)(BOOL animated);

@end

@implementation AddPasswordViewController{

...

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    if(self.viewDidDissapear){
        self.viewDidDissapear(animated);
    }
}

@end

1

コードで次の拡張機能を使用します:(Swift 4)

import UIKit

extension UINavigationController {

    func popViewController(animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        popViewController(animated: animated)
        CATransaction.commit()
    }

    func pushViewController(_ viewController: UIViewController, animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.