When dismissing a modal view controller using dismissViewController
, there is the option to provide a completion block. Is there a similar equivalent for
2020 Swift 5.1 way
This solution guarantee that completion is executed after popViewController is fully finished. You can test it by doing another operation on the NavigationController in completion: In all other solutions above the UINavigationController is still busy with popViewController operation and does not respond.
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)
}
}
Just for completeness, I've made an Objective-C category ready to use:
// 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
For iOS9 SWIFT version - works like a charm (hadn't tested for earlier versions). Based on 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()
}
}
}
There is a pod called UINavigationControllerWithCompletionBlock which adds support for a completion block when both pushing and popping on a UINavigationController.
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()
}
}
if you have this ...
navigationController?.popViewController(animated: false)
// I want this to happen next, help! ->
nextStep()
and you want to add a completion ...
CATransaction.begin()
navigationController?.popViewController(animated: true)
CATransaction.setCompletionBlock({ [weak self] in
self?.nextStep() })
CATransaction.commit()
it's that simple.
It's the same deal for the handy popToViewController
call.
A typical thing is you have an onboarding stack of a zillion screens. When finally done, you go all the way back to your "base" screen, and then finally fire up the app.
So in the "base" screen, to go "all the way back", 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()
}