The completion handler for the UINavigationController "pushViewController: animated"?

I am going to create an application using the UINavigationController to present the following view controllers. With iOS5, a new method for representing UIViewControllers :

 presentViewController:animated:completion: 

Now I ask why the completion handler for the UINavigationController does not work? There is only

 pushViewController:animated: 

Is it possible to create my own completion handler like the new presentViewController:animated:completion: :?

+87
handler ios uinavigationcontroller pushviewcontroller
Mar 28 2018-12-12T00:
source share
9 answers

See par answer for another and more modern solution.

UINavigationController animations start with CoreAnimation , so it makes sense to encapsulate the code inside CATransaction and thereby set the completion block.

Swift

For swift, I suggest creating an extension as such

 extension UINavigationController { public func pushViewController(viewController: UIViewController, animated: Bool, completion: @escaping (() -> Void)?) { CATransaction.begin() CATransaction.setCompletionBlock(completion) pushViewController(viewController, animated: animated) CATransaction.commit() } } 

Using:

 navigationController?.pushViewController(vc, animated: true) { // Animation done } 

Objective-c

Title:

 #import <UIKit/UIKit.h> @interface UINavigationController (CompletionHandler) - (void)completionhandler_pushViewController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion; @end 

Implementation:

 #import "UINavigationController+CompletionHandler.h" #import <QuartzCore/QuartzCore.h> @implementation UINavigationController (CompletionHandler) - (void)completionhandler_pushViewController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion { [CATransaction begin]; [CATransaction setCompletionBlock:completion]; [self pushViewController:viewController animated:animated]; [CATransaction commit]; } @end 
+116
Aug 10 '14 at 15:37
source share
β€” -

iOS 7+ Swift

Swift 4:

 // 2018.10.30 par: // I've updated this answer with an asynchronous dispatch to the main queue // when we're called without animation. This really should have been in the // previous solutions I gave but I forgot to add it. extension UINavigationController { public func pushViewController( _ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) { pushViewController(viewController, animated: animated) guard animated, let coordinator = transitionCoordinator else { DispatchQueue.main.async { completion() } return } coordinator.animate(alongsideTransition: nil) { _ in completion() } } func popViewController( animated: Bool, completion: @escaping () -> Void) { popViewController(animated: animated) guard animated, let coordinator = transitionCoordinator else { DispatchQueue.main.async { completion() } return } coordinator.animate(alongsideTransition: nil) { _ in completion() } } } 



EDIT: I added the version of my original Swift 3 answer. In this version, I removed the example of the collaborative animation shown in the Swift 2 version, as it seems to have confused many people.

Swift 3:

 import UIKit // Swift 3 version, no co-animation (alongsideTransition parameter is nil) extension UINavigationController { public func pushViewController( _ viewController: UIViewController, animated: Bool, completion: @escaping (Void) -> Void) { pushViewController(viewController, animated: animated) guard animated, let coordinator = transitionCoordinator else { completion() return } coordinator.animate(alongsideTransition: nil) { _ in completion() } } } 

Swift 2:

 import UIKit // Swift 2 Version, shows example co-animation (status bar update) extension UINavigationController { public func pushViewController( viewController: UIViewController, animated: Bool, completion: Void -> Void) { pushViewController(viewController, animated: animated) guard animated, let coordinator = transitionCoordinator() else { completion() return } coordinator.animateAlongsideTransition( // pass nil here or do something animated if you'd like, eg: { context in viewController.setNeedsStatusBarAppearanceUpdate() }, completion: { context in completion() } ) } } 
+57
Nov 17 '15 at 22:00
source share

UINavigationController does not currently support this. But there is a UINavigationControllerDelegate that you can use.

An easy way to do this is to subclass the UINavigationController and add a completion block property:

 @interface PbNavigationController : UINavigationController <UINavigationControllerDelegate> @property (nonatomic,copy) dispatch_block_t completionBlock; @end @implementation PbNavigationController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.delegate = self; } return self; } - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated { NSLog(@"didShowViewController:%@", viewController); if (self.completionBlock) { self.completionBlock(); self.completionBlock = nil; } } @end 

Before you click on the new view controller, you will need to install a completion block:

 UIViewController *vc = ...; ((PbNavigationController *)self.navigationController).completionBlock = ^ { NSLog(@"COMPLETED"); }; [self.navigationController pushViewController:vc animated:YES]; 

This new subclass can be assigned in Interface Builder or used programmatically as follows:

 PbNavigationController *nc = [[PbNavigationController alloc]initWithRootViewController:yourRootViewController]; 
+23
Feb 15 '13 at 16:44
source share

Based on the paired answer (which was the only one that worked with iOS9), but simpler and with the other missing (which could lead to the fact that the completion was never called):

 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() } } } 
+21
Jan 28 '16 at 15:02
source share

To extend the answer to @Klaas (and as a result of this question), I added completion blocks right away to the push method:

 @interface PbNavigationController : UINavigationController <UINavigationControllerDelegate> @property (nonatomic,copy) dispatch_block_t completionBlock; @property (nonatomic,strong) UIViewController * pushedVC; @end @implementation PbNavigationController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.delegate = self; } return self; } - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated { NSLog(@"didShowViewController:%@", viewController); if (self.completionBlock && self.pushedVC == viewController) { self.completionBlock(); } self.completionBlock = nil; self.pushedVC = nil; } -(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated { if (self.pushedVC != viewController) { self.pushedVC = nil; self.completionBlock = nil; } } -(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated completion:(dispatch_block_t)completion { self.pushedVC = viewController; self.completionBlock = completion; [self pushViewController:viewController animated:animated]; } @end 

Used as follows:

 UIViewController *vc = ...; [(PbNavigationController *)self.navigationController pushViewController:vc animated:YES completion:^ { NSLog(@"COMPLETED"); }]; 
+7
Feb 26 '14 at 15:14
source share

Starting with iOS 7.0, you can use the UIViewControllerTransitionCoordinator to add a push completion block:

 UINavigationController *nav = self.navigationController; [nav pushViewController:vc animated:YES]; id<UIViewControllerTransitionCoordinator> coordinator = vc.transitionCoordinator; [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { } completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { NSLog(@"push completed"); }]; 
+5
Aug 18 '16 at 20:41
source share

Here is the Swift 4 version with Pop.

 extension UINavigationController { public func pushViewController(viewController: UIViewController, animated: Bool, completion: (() -> Void)?) { CATransaction.begin() CATransaction.setCompletionBlock(completion) pushViewController(viewController, animated: animated) CATransaction.commit() } public func popViewController(animated: Bool, completion: (() -> Void)?) { CATransaction.begin() CATransaction.setCompletionBlock(completion) popViewController(animated: animated) CATransaction.commit() } } 

Just in case someone needs this.

+5
Feb 06 '18 at 20:36
source share

Swift 2.0

 extension UINavigationController : UINavigationControllerDelegate { private struct AssociatedKeys { static var currentCompletioObjectHandle = "currentCompletioObjectHandle" } typealias Completion = @convention(block) (UIViewController)->() var completionBlock:Completion?{ get{ let chBlock = unsafeBitCast(objc_getAssociatedObject(self, &AssociatedKeys.currentCompletioObjectHandle), Completion.self) return chBlock as Completion }set{ if let newValue = newValue { let newValueObj : AnyObject = unsafeBitCast(newValue, AnyObject.self) objc_setAssociatedObject(self, &AssociatedKeys.currentCompletioObjectHandle, newValueObj, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } func popToViewController(animated: Bool,comp:Completion){ if (self.delegate == nil){ self.delegate = self } completionBlock = comp self.popViewControllerAnimated(true) } func pushViewController(viewController: UIViewController, comp:Completion) { if (self.delegate == nil){ self.delegate = self } completionBlock = comp self.pushViewController(viewController, animated: true) } public func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool){ if let comp = completionBlock{ comp(viewController) completionBlock = nil self.delegate = nil } } } 
+3
Dec 24 '15 at 6:46
source share

A little more pipelines are required to add this behavior, and you still have the opportunity to install an external delegate.

Here's a documented implementation that supports delegate functions:

LBXCompletingNavigationController

+2
Mar 02 '14 at 1:36
source share



All Articles