popViewController的完成块

时间:2012-10-15 21:36:31

标签: ios cocoa-touch uiview uiviewcontroller uiviewanimation

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

完成论证非常方便。例如,我可以使用它来阻止从tableview中删除一行,直到模态关闭屏幕,让用户看到行动画。当从推出的视图控制器返回时,我希望有同样的机会。

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

如果没有这样的方法,有什么办法吗?

20 个答案:

答案 0 :(得分:177)

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

  

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

这在技术上是正确的,因为UINavigationController API并未为此提供任何选项。但是,通过使用CoreAnimation框架,可以将完成块添加到基础动画中:

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

popViewControllerAnimated:使用的动画结束后,将立即调用完成块。此功能自iOS 4开始提供。

答案 1 :(得分:45)

对于 iOS9 SWIFT版本 - 就像一个魅力(未经过早期版本测试)。基于this answer

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

答案 2 :(得分:27)

我制作了Swift版本,附带 @JorisKluivers 答案。

这将在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()
    }
}

答案 3 :(得分:16)

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

答案 4 :(得分:15)

没有办法做你想要开箱即用的东西。即,没有方法可以使用完成块从导航堆栈中弹出视图控制器。

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

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

答案 5 :(得分:10)

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

答案 6 :(得分:7)

基于@ HotJard的答案,当你想要的只是几行代码时。快速而简单。

Swift 4

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

答案 7 :(得分:6)

SWIFT 4.1

 $token = (Invoke-WebRequest -Uri "https://stage-ansible-tower.hc.cloud.uk.hsbc/api/v1/authtoken/"  -Body $json -ContentType "application/json" -Method POST).content | ConvertFrom-Json

答案 8 :(得分: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()
    }
}

答案 9 :(得分:4)

在显示的视图控制器上调用viewDidDisappear方法后调用完成块,因此将代码放在弹出视图控制器的viewDidDisappear方法中应该与完成块相同。

答案 10 :(得分:3)

2018年......

如果你有这个......

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

并且您想要添加完成...

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

就这么简单。

答案 11 :(得分:2)

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

答案 12 :(得分: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)
    }
}

答案 13 :(得分:2)

请参考最新版本(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()
    }
}

答案 14 :(得分: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()
    }
}

答案 15 :(得分: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

答案 16 :(得分:1)

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

答案 17 :(得分:1)

根据this answer清理 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()
        }
    }
}

答案 18 :(得分: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

答案 19 :(得分:0)

我认为 viewDidDisappear(_ animation: Bool) 函数可以帮助解决这个问题。当视图完全消失时,它会被调用。

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