我看到的唯一解决方案是回答stackoverflow问题。我发布了以下链接。我所指的答案是第五个。但是,似乎某些用户在解决方案方面存在一些问题。我不知道是否还有其他类别可以阻止同时推送两个控制器。任何提示或建议都表示赞赏。
#import "UINavigationController+Consistent.h"
#import <objc/runtime.h>
/// This char is used to add storage for the isPushingViewController property.
static char const * const ObjectTagKey = "ObjectTag";
@interface UINavigationController ()
@property (readwrite,getter = isViewTransitionInProgress) BOOL viewTransitionInProgress;
@end
@implementation UINavigationController (Consistent)
- (void)setViewTransitionInProgress:(BOOL)property {
NSNumber *number = [NSNumber numberWithBool:property];
objc_setAssociatedObject(self, ObjectTagKey, number , OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)isViewTransitionInProgress {
NSNumber *number = objc_getAssociatedObject(self, ObjectTagKey);
return [number boolValue];
}
#pragma mark - Intercept Pop, Push, PopToRootVC
/// @name Intercept Pop, Push, PopToRootVC
- (NSArray *)safePopToRootViewControllerAnimated:(BOOL)animated {
if (self.viewTransitionInProgress) return nil;
if (animated) {
self.viewTransitionInProgress = YES;
}
//-- This is not a recursion, due to method swizzling the call below calls the original method.
return [self safePopToRootViewControllerAnimated:animated];
}
- (NSArray *)safePopToViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (self.viewTransitionInProgress) return nil;
if (animated) {
self.viewTransitionInProgress = YES;
}
//-- This is not a recursion, due to method swizzling the call below calls the original method.
return [self safePopToViewController:viewController animated:animated];
}
- (UIViewController *)safePopViewControllerAnimated:(BOOL)animated {
if (self.viewTransitionInProgress) return nil;
if (animated) {
self.viewTransitionInProgress = YES;
}
//-- This is not a recursion, due to method swizzling the call below calls the original method.
return [self safePopViewControllerAnimated:animated];
}
- (void)safePushViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.delegate = self;
//-- If we are already pushing a view controller, we dont push another one.
if (self.isViewTransitionInProgress == NO) {
//-- This is not a recursion, due to method swizzling the call below calls the original method.
[self safePushViewController:viewController animated:animated];
if (animated) {
self.viewTransitionInProgress = YES;
}
}
}
// This is confirmed to be App Store safe.
// If you feel uncomfortable to use Private API, you could also use the delegate method navigationController:didShowViewController:animated:.
- (void)safeDidShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
//-- This is not a recursion. Due to method swizzling this is calling the original method.
[self safeDidShowViewController:viewController animated:animated];
self.viewTransitionInProgress = NO;
}
// If the user doesnt complete the swipe-to-go-back gesture, we need to intercept it and set the flag to NO again.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
id<UIViewControllerTransitionCoordinator> tc = navigationController.topViewController.transitionCoordinator;
[tc notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.viewTransitionInProgress = NO;
//--Reenable swipe back gesture.
self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController;
[self.interactivePopGestureRecognizer setEnabled:YES];
}];
//-- Method swizzling wont work in the case of a delegate so:
//-- forward this method to the original delegate if there is one different than ourselves.
if (navigationController.delegate != self) {
[navigationController.delegate navigationController:navigationController
willShowViewController:viewController
animated:animated];
}
}
+ (void)load {
//-- Exchange the original implementation with our custom one.
method_exchangeImplementations(class_getInstanceMethod(self, @selector(pushViewController:animated:)), class_getInstanceMethod(self, @selector(safePushViewController:animated:)));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(didShowViewController:animated:)), class_getInstanceMethod(self, @selector(safeDidShowViewController:animated:)));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(popViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopViewControllerAnimated:)));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToRootViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopToRootViewControllerAnimated:)));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToViewController:animated:)), class_getInstanceMethod(self, @selector(safePopToViewController:animated:)));
}
@end
答案 0 :(得分:3)
更新回答:
我更喜欢nonamelive
在Github上的解决方案与我最初发布的内容:https://gist.github.com/nonamelive/9334458。通过继承UINavigationController
并利用UINavigationControllerDelegate
,您可以确定何时发生转换,防止在转换期间发生其他转换,并在同一个类中进行所有转换。以下是非专用解决方案的更新,其中不包含私有API:
#import "NavController.h"
@interface NavController ()
@property (nonatomic, assign) BOOL shouldIgnorePushingViewControllers;
@end
@implementation NavController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (!self.shouldIgnorePushingViewControllers)
{
[super pushViewController:viewController animated:animated];
}
self.shouldIgnorePushingViewControllers = YES;
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.shouldIgnorePushingViewControllers = NO;
}
@end
上一个回答:
上一个答案的问题:
isBeingPresented
和isBeingDismissed
仅适用于viewDidLoad:
或viewDidApper:
虽然我自己没有对此进行测试,但这是一个建议。
由于您使用的是UINavigationController
,因此您可以访问导航堆栈的内容,如下所示:
NSArray *viewControllers = self.navigationController.viewControllers;
通过该视图控制器数组,您可以根据需要访问部分或全部相关索引。
幸运的是,在iOS 5中引入了两种特别方便的方法:isBeingPresented和isBeingDismissed返回&#34; YES&#34;如果视图控制器分别处于呈现或被解雇的过程中; &#34; NO&#34;否则。
因此,举例来说,这是一种方法:
NSArray *viewControllers = self.navigationController.viewControllers;
for (UIViewController *viewController in viewControllers) {
if (viewController.isBeingPresented || viewController.isBeingDismissed) {
// In this case when a pop or push is already in progress, don't perform
// a pop or push on the current view controller. Perhaps return to this
// method after a delay to check this conditional again.
return;
}
}
// Else if you make it through the loop uninterrupted, perform push or pop
// of the current view controller.
实际上,您可能不必遍历堆栈中的每个视图控制器,但是这个建议可能会帮助您摆脱困境。
答案 1 :(得分:1)
这是我的方法,使用UINavigationController类别和方法调配。
方法-[UINavigationController didShowViewController:animated:]
是私有的,因此虽然报告使用它是安全的,但请自担风险。
对于该想法,积分转到this answer,对于方法混合代码,积分转到NSHipster。 This answer也有一个有趣的方法。
//
// UINavigationController+Additions.h
//
@interface UINavigationController (Additions)
@property (nonatomic, getter = isViewTransitionInProgress) BOOL viewTransitionInProgress;
@end
//
// UINavigationController+Additions.m
//
#import "UINavigationController+Additions.h"
#import <objc/runtime.h>
static void *UINavigationControllerViewTransitionInProgressKey = &UINavigationControllerViewTransitionInProgressKey;
@interface UINavigationController ()
// Private method, use at your own risk.
- (void)didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
@end
@implementation UINavigationController (Additions)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector1 = @selector(pushViewController:animated:);
SEL swizzledSelector1 = @selector(zwizzledForViewTransitionInProgress_pushViewController:animated:);
Method originalMethod1 = class_getInstanceMethod(class, originalSelector1);
Method swizzledMethod1 = class_getInstanceMethod(class, swizzledSelector1);
BOOL didAddMethod1 = class_addMethod(class, originalSelector1, method_getImplementation(swizzledMethod1), method_getTypeEncoding(swizzledMethod1));
if (didAddMethod1) {
class_replaceMethod(class, swizzledSelector1, method_getImplementation(originalMethod1), method_getTypeEncoding(originalMethod1));
} else {
method_exchangeImplementations(originalMethod1, swizzledMethod1);
}
SEL originalSelector2 = @selector(didShowViewController:animated:);
SEL swizzledSelector2 = @selector(zwizzledForViewTransitionInProgress_didShowViewController:animated:);
Method originalMethod2 = class_getInstanceMethod(class, originalSelector2);
Method swizzledMethod2 = class_getInstanceMethod(class, swizzledSelector2);
BOOL didAddMethod2 = class_addMethod(class, originalSelector2, method_getImplementation(swizzledMethod2), method_getTypeEncoding(swizzledMethod2));
if (didAddMethod2) {
class_replaceMethod(class, swizzledSelector2, method_getImplementation(originalMethod2), method_getTypeEncoding(originalMethod2));
} else {
method_exchangeImplementations(originalMethod2, swizzledMethod2);
}
});
}
- (void)zwizzledForViewTransitionInProgress_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (self.viewTransitionInProgress) {
LogWarning(@"Pushing a view controller while an other view transition is in progress. Aborting.");
} else {
self.viewTransitionInProgress = YES;
[self zwizzledForViewTransitionInProgress_pushViewController:viewController animated:animated];
}
}
- (void)zwizzledForViewTransitionInProgress_didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[self zwizzledForViewTransitionInProgress_didShowViewController:viewController animated:YES];
self.viewTransitionInProgress = NO;
}
- (void)setViewTransitionInProgress:(BOOL)viewTransitionInProgress
{
NSNumber *boolValue = [NSNumber numberWithBool:viewTransitionInProgress];
objc_setAssociatedObject(self, UINavigationControllerViewTransitionInProgressKey, boolValue, OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)isViewTransitionInProgress
{
NSNumber *viewTransitionInProgress = objc_getAssociatedObject(self, UINavigationControllerViewTransitionInProgressKey);
return [viewTransitionInProgress boolValue];
}
@end
答案 2 :(得分:1)
受@Lindsey Scott的启发,我创建了UINavigationController子类。我的解决方案的优点是它还处理弹出,你可以实际执行所有请求而没有问题(这是通过acceptConflictingCommands标志控制的。)
MyNavigationController.h
#import <UIKit/UIKit.h>
@interface MyNavigationController : UINavigationController
@property(nonatomic, assign) BOOL acceptConflictingCommands;
@end
MyNavigationController.m
#import "MyNavigationController.h"
@interface MyNavigationController ()<UINavigationControllerDelegate>
@property(nonatomic, assign) BOOL shouldIgnoreStackRequests;
@property(nonatomic, strong) NSMutableArray* waitingCommands;
@end
@implementation MyNavigationController
-(instancetype)init
{
if( self = [super init] )
{
self.delegate = self;
_waitingCommands = [NSMutableArray new];
}
return self;
}
-(instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
if( self = [super initWithRootViewController:rootViewController] )
{
self.delegate = self;
_waitingCommands = [NSMutableArray new];
_acceptConflictingCommands = YES;
}
return self;
}
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if( !_shouldIgnoreStackRequests )
{
[super pushViewController:viewController animated:animated];
_shouldIgnoreStackRequests = YES;
}
else if (_acceptConflictingCommands)
{
__weak typeof(self) weakSelf = self;
//store and push it after current transition ends
[_waitingCommands addObject:^{
id strongSelf = weakSelf;
[strongSelf pushViewController:viewController animated:animated];
}];
}
}
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
{
__block UIViewController* popedController = nil;
if( 1 < self.viewControllers.count )
{
if( !_shouldIgnoreStackRequests )
{
popedController = [super popViewControllerAnimated:animated];
_shouldIgnoreStackRequests = YES;
}
else if( _acceptConflictingCommands )
{
__weak typeof(self) weakSelf = self;
[_waitingCommands addObject:^{
id strongSelf = weakSelf;
popedController = [strongSelf popViewControllerAnimated:animated];
}];
}
}
return popedController;
}
#pragma mark - uinavigationcontroller delegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
_shouldIgnoreStackRequests = NO;
if( 0 < _waitingCommands.count )
{
void(^waitingAction)() = _waitingCommands.lastObject;
[_waitingCommands removeLastObject];
waitingAction();
}
}
@end
当然,您可以更改acceptConflictingCommands的默认值或从外部控制它。
如果您的代码恰好使用popToRootViewController,setViewControllers:animated:和/或popToViewController,您必须以相同的方式覆盖它们,以确保它们不会制动导航堆栈。