UINavigationController“pushViewController:animated”的完成处理程序?

问题描述 投票:0回答:10

我要使用

UINavigationController
创建一个应用程序来呈现下一个视图控制器。 iOS5 有一种新的呈现方式
UIViewControllers
:

presentViewController:animated:completion:

现在我问我为什么没有

UINavigationController
的完成处理程序? 只有

pushViewController:animated:

是否可以像新的

presentViewController:animated:completion:
一样创建我自己的完成处理程序?

ios uinavigationcontroller handler pushviewcontroller
10个回答
156
投票

请参阅 par 的答案,了解另一个更新的解决方案

UINavigationController
动画与
CoreAnimation
一起运行,因此将代码封装在
CATransaction
中并因此设置完成块是有意义的。

斯威夫特

为了快速,我建议创建一个这样的扩展

extension UINavigationController {

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

}

用途:

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

Objective-C

标题:

#import <UIKit/UIKit.h>

@interface UINavigationController (CompletionHandler)

- (void)completionhandler_pushViewController:(UIViewController *)viewController
                                    animated:(BOOL)animated
                                  completion:(void (^)(void))completion;

@end

实施:

#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

134
投票

iOS 7+ 斯威夫特

斯威夫特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() }
    }
}

编辑:我添加了原始答案的 Swift 3 版本。在此版本中,我删除了 Swift 2 版本中显示的示例联合动画,因为它似乎让很多人感到困惑。

斯威夫特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() }
    }
}

斯威夫特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()
            }
        )
    }
}

38
投票

基于par的答案(这是唯一适用于iOS9的答案),但更简单并且缺少else(这可能导致完成永远不会被调用):

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

25
投票

目前

UINavigationController
不支持此功能。但是您可以使用
UINavigationControllerDelegate

实现此目的的一个简单方法是子类化

UINavigationController
并添加完成块属性:

@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

在推送新的视图控制器之前,您必须设置完成块:

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

这个新子类可以在 Interface Builder 中分配,也可以像这样以编程方式使用:

PbNavigationController *nc = [[PbNavigationController alloc]initWithRootViewController:yourRootViewController];

10
投票

这里是带有 Pop 的 Swift 4 版本。

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

以防万一其他人需要这个。


9
投票

为了扩展@Klaas的答案(以及由于this问题),我已将完成块直接添加到push方法中:

@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

使用方法如下:

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

6
投票

从iOS 7.0开始,您可以使用

UIViewControllerTransitionCoordinator
添加推送完成块:

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");
}];

4
投票

斯威夫特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
        }
    }
}

2
投票

添加此行为并保留设置外部委托的能力需要更多的管道工作。

这是维护委托功能的记录实现:

LBX完成导航控制器


0
投票

我已经写了一个扩展,但就我而言,方法的调用非常混乱,所以这种方式不适合我。无论在哪里,也许有人会需要它。

private var UINavigationControllerCompletionKey = 0
private var UINavigationControllerDelegateKey = 1

extension UINavigationController {
    fileprivate var completion: EmptyClosure? {
        get {
            return objc_getAssociatedObject(self, &UINavigationControllerCompletionKey) as? EmptyClosure
        }
        set(newValue) {
            objc_setAssociatedObject(self, &UINavigationControllerCompletionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    /// Need to save delegate in order to set it back after completion
    fileprivate var previousDelegateCache: UINavigationControllerDelegate? {
        get {
            return objc_getAssociatedObject(self, &UINavigationControllerDelegateKey) as? UINavigationControllerDelegate
        }
        set(newValue) {
            objc_setAssociatedObject(self, &UINavigationControllerDelegateKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    @discardableResult
    public func popViewController(animated: Bool, completion: @escaping EmptyClosure) -> UIViewController? {
        self.completion = completion
        previousDelegateCache = delegate
        delegate = self
        return self.popViewController(animated: animated)
    }

    @discardableResult
    public func popToRootViewController(animated: Bool, completion: @escaping EmptyClosure) -> [UIViewController]? {
        self.completion = completion
        previousDelegateCache = delegate
        delegate = self
        return self.popToRootViewController(animated: animated)
    }

    public func pushViewController(_ viewController: UIViewController, animated: Bool, completion: EmptyClosure?) {
        self.completion = completion
        previousDelegateCache = delegate
        delegate = self
        return self.pushViewController(viewController, animated: animated)
    }
}

extension UINavigationController: UINavigationControllerDelegate {
    public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        completion?()
        completion = nil
        delegate = previousDelegateCache
        previousDelegateCache = nil
    }
}  

还可以使用缓冲的 NavigationController 检查有趣的存储库。 https://github.com/Plasma/BufferedNavigationController/tree/master 虽然delegate的问题依然存在其中。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.