我要使用
UINavigationController
创建一个应用程序来呈现下一个视图控制器。
iOS5 有一种新的呈现方式 UIViewControllers
:
presentViewController:animated:completion:
现在我问我为什么没有
UINavigationController
的完成处理程序?
只有
pushViewController:animated:
是否可以像新的
presentViewController:animated:completion:
一样创建我自己的完成处理程序?
请参阅 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
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()
}
)
}
}
基于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()
}
}
}
目前
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];
这里是带有 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()
}
}
以防万一其他人需要这个。
为了扩展@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");
}];
从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");
}];
斯威夫特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
}
}
}
我已经写了一个扩展,但就我而言,方法的调用非常混乱,所以这种方式不适合我。无论在哪里,也许有人会需要它。
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的问题依然存在其中。