I\'m about creating an app using a UINavigationController
to present the next view controllers.
With iOS5 there´s a new method to presenting UIViewControl
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've added a Swift 3 version of my original answer. In this version I've removed the example co-animation shown in the Swift 2 version as it seems to have confused a lot of 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, e.g.:
{ context in
viewController.setNeedsStatusBarAppearanceUpdate()
},
completion: { context in
completion()
}
)
}
}
It takes a little more pipework to add this behavior and retain the ability to set an external delegate.
Here's a documented implementation that maintains delegate functionality:
LBXCompletingNavigationController
Currently the UINavigationController
does not support this. But there's the UINavigationControllerDelegate
that you can use.
An easy way to accomplish this is by subclassing UINavigationController
and adding 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 pushing the new view controller you would have to set the completion block:
UIViewController *vc = ...;
((PbNavigationController *)self.navigationController).completionBlock = ^ {
NSLog(@"COMPLETED");
};
[self.navigationController pushViewController:vc animated:YES];
This new subclass can either be assigned in Interface Builder or be used programmatically like this:
PbNavigationController *nc = [[PbNavigationController alloc]initWithRootViewController:yourRootViewController];