web-dev-qa-db-ja.com

popViewControllerの完了ブロック

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

補完引数は非常に便利です。たとえば、モーダルが画面から外れるまでTableViewから行を削除しないようにして、ユーザーに行のアニメーションを表示させることができます。プッシュされたView Controllerから戻るとき、同じ機会が欲しいです。

popViewControllerアニメーションブロックにUIViewを配置しようとしましたが、完了ブロックにアクセスできます。ただし、これにより、ポップされるビューにいくつかの望ましくない副作用が生じます。

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

99
Ben Packard

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

すぐに使用したいことをする方法はありません

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

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

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

180
Joris Kluivers

iOS9Swiftバージョンの場合-チャームのように動作します(以前のバージョンではテストされていませんでした)。 この回答 に基づく

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()
        }
    }
}
49
HotJard

@ JorisKluivers answerを使用して、拡張子付きのSwiftバージョンを作成しました。

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

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()
    }
}
29
Arbitur

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

- (void) navigationController:(UINavigationController *) navigationController didShowViewController:(UIViewController *) viewController animated:(BOOL) animated {
    if (_completion) {
        _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;
}
16
Jos Jong

すぐに使用したいことを行う方法はありません。つまり、navスタックからView Controllerをポップするための完了ブロックを持つメソッドはありません。

私がやることは、ロジックをviewDidAppearに入れることです。ビューが画面に表示されたときに呼び出されます。表示されるView Controllerのすべての異なるシナリオに対して呼び出されますが、それは問題ないはずです。

または、UINavigationControllerDelegateメソッドnavigationController:didShowViewController:animated:同様のことを行います。これは、Navigation ControllerがView Controllerのプッシュまたはポップを完了したときに呼び出されます。

15
mattjgalloway

アニメーションの有無にかかわらず適切に動作し、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)
  }
}
12
rshev

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

Swift 4

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

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: true)
    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()
}
}
8
Muhammad Waqas

この回答のおかげで、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()
    }
}
5
Benobab

2018年に向けて...

これがあれば...

    navigationController?.popViewController(animated: false)
    NeXTSTEP()

補完を追加したい...

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

とても簡単です。

4
Fattie

提示されたView ControllerでviewDidDisappearメソッドが呼び出された後に完了ブロックが呼び出されるため、ポップされたView ControllerのviewDidDisappearメソッドにコードを配置すると、完了ブロックと同じように動作します。

4
rdelmar

特定のものにポップするためのオプションの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()
        }
    }
}
2
TejAces

INavigationControllerWithCompletionBlock と呼ばれるポッドがあります。これは、UINavigationControllerでプッシュとポップの両方を行うときに完了ブロックのサポートを追加します。

2
duncanc4

クリーンアップ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
d4Rk

ブロックを使用して正確にこれを正確に達成しました。取得した結果コントローラーに、モーダルビューによって追加された行を、画面から完全に離れてから一度だけ表示して、ユーザーが変更を確認できるようにしたかったのです。モーダルビューコントローラーの表示を担当するセグエの準備では、モーダルが消えたときに実行するブロックを設定します。モーダルビューコントローラーでは、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
malhal

コードで次の拡張機能を使用します:(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()
    }
}

完全を期すために、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
Diego Freniche