Completion handler for UINavigationController “pushViewController:animated”?

前端 未结 9 1006
礼貌的吻别
礼貌的吻别 2020-11-30 18:08

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

相关标签:
9条回答
  • 2020-11-30 18:10

    Since iOS 7.0,you can use 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");
    }];
    
    0 讨论(0)
  • 2020-11-30 18:11

    Here is the Swift 4 version with the 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 else needs this.

    0 讨论(0)
  • 2020-11-30 18:13

    See par's answer for another and more up to date solution

    UINavigationController animations are run with CoreAnimation, so it would make sense to encapsulate the code within CATransaction and thus set a 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()
      }
    
    }
    

    Usage:

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

    Objective-C

    Header:

    #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
    
    0 讨论(0)
  • 2020-11-30 18:18

    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
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 18:23

    Based on par's answer (which was the only one that worked with iOS9), but simpler and with a missing else (which could have led to the completion never being 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()
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 18:24

    To expand on @Klaas' answer (and as a result of this question) I've added completion blocks directly 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
    

    To be used as follows:

    UIViewController *vc = ...;
    [(PbNavigationController *)self.navigationController pushViewController:vc animated:YES completion:^ {
        NSLog(@"COMPLETED");
    }];
    
    0 讨论(0)
提交回复
热议问题