127

使用 关闭模态视图控制器dismissViewController时,可以选择提供完成块。是否有类似的等价物popViewController

完成参数非常方便。例如,我可以使用它来推迟从 tableview 中删除一行,直到模态框离开屏幕,让用户看到行动画。从推送视图控制器返回时,我想要同样的机会。

我试过放置popViewController一个UIView动画块,在那里我可以访问一个完成块。但是,这会对弹出的视图产生一些不需要的副作用。

如果没有这样的方法可用,有什么解决方法?

4

21 回答 21

216

我知道两年前已经接受了一个答案,但是这个答案是不完整的。

开箱即用没有办法做你想做的事

这在技术上是正确的,因为UINavigationControllerAPI 没有为此提供任何选项。但是,通过使用 CoreAnimation 框架,可以将完成块添加到底层动画:

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

一旦使用的动画popViewControllerAnimated:结束,就会调用完成块。此功能从 iOS 4 开始可用。

于 2014-12-01T13:56:14.243 回答
65

Swift 5 版本 - 就像一个魅力。基于这个答案

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

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

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

        if animated, let coordinator = transitionCoordinator {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}
于 2016-04-23T10:51:31.677 回答
39

我制作了一个Swift带有@JorisKluivers答案的扩展版本。

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()
    }
}
于 2015-01-30T09:14:41.047 回答
18

斯威夫特 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()
}
}
于 2018-09-14T15:45:26.057 回答
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;
}
于 2013-12-13T11:48:02.523 回答
16

根据@HotJard 的回答,当您只需要几行代码时。快捷方便。

斯威夫特 4

_ = self.navigationController?.popViewController(animated: true)
self.navigationController?.transitionCoordinator.animate(alongsideTransition: nil) { _ in
    doWhatIWantAfterContollerHasPopped()
}
于 2018-05-25T15:50:30.767 回答
15

没有办法做你想要的开箱即用。即没有用于从导航堆栈弹出视图控制器的完成块的方法。

我要做的是将逻辑放入viewDidAppear. 当视图完成出现在屏幕上时,将调用它。视图控制器出现的所有不同场景都会调用它,但这应该没问题。

或者您可以使用该UINavigationControllerDelegate方法navigationController:didShowViewController:animated:做类似的事情。这在导航控制器完成推送或弹出视图控制器时调用。

于 2012-10-15T21:39:44.063 回答
15

正确使用或不使用动画,还包括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)
  }
}
于 2016-09-06T14:51:48.170 回答
9

对于 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()
}
于 2018-05-18T17:07:34.110 回答
6

根据这个答案清理了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()
        }
    }
}
于 2018-06-19T09:30:10.613 回答
5

在呈现的视图控制器上调用 viewDidDisappear 方法之后调用完成块,因此将代码放入弹出视图控制器的 viewDidDisappear 方法应该与完成块一样工作。

于 2012-10-15T21:51:51.300 回答
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()
    }
}
于 2016-11-09T08:06:27.747 回答
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()
        }
    }
}
于 2018-06-17T20:08:57.350 回答
3

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)
    }
}
于 2020-02-06T08:34:17.173 回答
3

请参考 Swifty & SDK-like 方式的最新版本(5.1),

extension UINavigationController {
    func popViewController(animated: Bool, completion: (() -> ())? = nil) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        popViewController(animated: animated)
        CATransaction.commit()
    }
    func pushViewController(_ viewController: UIViewController, animated: Bool, completion: (() -> ())? = nil) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }
}
于 2021-04-06T00:36:41.523 回答
2

有一个名为UINavigationControllerWithCompletionBlock的 pod ,它在 UINavigationController 上推送和弹出时添加了对完成块的支持。

于 2016-02-15T23:33:41.103 回答
2

在您的代码中使用下一个扩展:(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()
    }
}
于 2018-01-04T21:42:55.283 回答
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
于 2015-11-27T12:15:18.423 回答
1

我使用块精确地实现了这一点。我希望我的获取结果控制器显示模式视图添加的行,只有在它完全离开屏幕后,用户才能看到发生的变化。在为负责显示模态视图控制器的 segue 做准备时,我设置了当模态消失时要执行的块。在模态视图控制器中,我覆盖 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
于 2015-12-27T01:34:26.250 回答
0

我认为 viewDidDisappear(_ animated: Bool) 函数可以对此有所帮助。当视图完全消失时,它将被调用。

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    //do the stuff here
}
于 2021-05-28T10:09:20.793 回答
0

2021 年,斯威夫特 5

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(animated: Bool, completion: @escaping() -> ()) {
    popViewController(animated: animated)
    
    if let coordinator = transitionCoordinator, animated {
        coordinator.animate(alongsideTransition: nil) { _ in
            completion()
        }
    } else {
        completion()
    }
}
}
于 2021-09-30T05:48:15.237 回答