PopViewController 的完成块

当使用 dismissViewController忽略模态视图控制器时,可以选择提供一个完成块。popViewController有类似的等价物吗?

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

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

如果没有这样的方法可用,那么有哪些变通方法?

73666 次浏览

没有办法做到你想要的东西,打开盒子。也就是说,没有一个具有完成块的方法用于从导航堆栈中弹出视图控制器。

我要做的是把逻辑放到 viewDidAppear中。当屏幕上的视图完成后,将调用这个函数。视图控制器出现的所有不同场景都会调用它,但这应该没问题。

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

完成块是在视图控制器上调用 viewDidDis卖方法之后调用的,因此将代码放入弹出视图控制器的 viewDidDis卖方法中应该与完成块的工作原理相同。

我也有同样的问题。因为我必须在多个场合使用它,并在完成块链中使用它,所以我在 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;
}

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

没有办法做到你想要的东西,打开盒子

这在技术上是正确的,因为 UINavigationController API 没有为此提供任何选项。然而,通过使用 CoreAnimation 框架,可以向底层动画添加一个完成块:

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


[self.navigationController popViewControllerAnimated:YES];


[CATransaction commit];

一旦 popViewControllerAnimated:使用的动画结束,就会调用完成块。这个功能从 iOS4开始就可以使用了。

我做了一个带有扩展 @ JorisKluivers答案的 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()
}
}

为了完整起见,我准备了一个 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

我用一块积木精确地做到了这一点。我希望我提取的结果控制器显示模态视图添加的行,只有当它完全离开屏幕时,用户才能看到发生的变化。在准备显示模态视图控制器的 segue 时,我设置了模态消失时要执行的块。在模态视图控制器中,我覆盖 viewDidDissapear,然后调用块。我只是在模态出现时开始更新,在模态消失时结束更新,但这是因为我使用的是 NSFetcheResultsController,您可以在代码块中做任何您想做的事情。

-(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

有一个叫做 UINavigationControllerWithCompletionBlock的吊舱,它在 UINavigationController 上同时按压和弹出时增加了对完成块的支持。

迅捷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()
}
}
}

正确使用或不使用动画,还包括 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)
}
}

快速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()
}
}

使用代码的下一个扩展名: (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年。

如果你有这个..。

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

根据@HotJard 的回答,当你想要的只是几行代码时,快速简单。

斯威夫特4:

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

带有可选 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()
}
}
}

清理了基于 这个答案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()
}
}
}

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

2020年斯威夫特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)
}
}

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

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

我认为 viewDidDisaper (_ Animated: Bool)函数可以帮助解决这个问题。它将在视图完全消失时调用。

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
//do the stuff here
}

我发现 func navigationController(UINavigationController, didShow: UIViewController, animated: Bool)的实现是这里唯一可行的解决方案。 我们可以使用 RxSwift:

import UIKit
import RxSwift
import RxCocoa


extension Reactive where Base: UINavigationController {
func popToViewController(_ viewController: UIViewController, animated: Bool) -> ControlEvent<ShowEvent> {
let source = didShow
.filter { [weak viewController] event in
viewController == event.0
}
.take(1)
        

_ = base.popToViewController(viewController, animated: animated)
        

return ControlEvent(events: source)
}
}

用法:


// let navigationController = UINavigationController(rootViewController: page1)
// navigationController.pushViewController(page2, animated: false)


navigationController.rx
.popToViewController(page1, animated: true)
.bind { _ in
// pop completion
}
.disposed(by: disposeBag)