iOS:目前是否有办法阻止同时推送或弹出两个视图控制器?

我见过的唯一解决方案是回答stackoverflow问题。 我发布了以下链接。 我所指的答案是第五个。 但是,似乎有些用户在使用该解决方案时遇到了一些问题。 我不知道是否有另一个类别可以防止同时推送两个控制器。 任何提示或建议表示赞赏。

#import "UINavigationController+Consistent.h" #import  /// 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 tc = navigationController.topViewController.transitionCoordinator; [tc notifyWhenInteractionEndsUsingBlock:^(id context) { self.viewTransitionInProgress = NO; //--Reenable swipe back gesture. self.interactivePopGestureRecognizer.delegate = (id)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 

iOS应用程序错误 – 无法将self添加为子视图

更新的答案:

我更喜欢Github上的nonamelive解决方案,我最初发布的内容: https : nonamelive 。 通过UINavigationController并利用UINavigationControllerDelegate ,您可以确定转换何时发生,防止在转换期间发生其他转换,并在同一个类中完成所有转换。 这是nonamelive解决方案的更新,它排除了私有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 

上一个答案:

这个问题以前的答案: isBeingPresentedisBeingDismissed仅适用于viewDidLoad:viewDidApper:

虽然我自己没有测试过,但这是一个建议。

由于您使用的是UINavigationController ,因此可以访问导航堆栈的内容,如下所示:

 NSArray *viewControllers = self.navigationController.viewControllers; 

通过该视图控制器数组,您可以根据需要访问部分或全部相关索引。

幸运的是,iOS 5中引入了两种特别方便的方法: isBeingPresented和isBeingDismissed ,如果视图控制器分别处于呈现或被解除的过程中,则返回“YES”; 否则“不”。

所以,例如,这是一种方法:

 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. 

实际上,您可能不必遍历堆栈中的每个视图控制器,但是这个建议可能会帮助您摆脱困境。

这是我的方法,使用UINavigationController类和方法调配。 方法-[UINavigationController didShowViewController:animated:]是私有的,因此尽管报告使用它是安全的,但请自行承担风险。

对于这个想法和WTHipster的方法调整代码,Credits 回答了这个问题的答案 。 这个答案也有一个有趣的方法。

 // // UINavigationController+Additions.h // @interface UINavigationController (Additions) @property (nonatomic, getter = isViewTransitionInProgress) BOOL viewTransitionInProgress; @end // // UINavigationController+Additions.m // #import "UINavigationController+Additions.h" #import  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 

受@Lindsey Scott的启发,我创建了UINavigationController子类。 我的解决方案的优点是它还可以处理弹出,你可以实际执行所有请求而不会出现问题(这是通过acceptConflictingCommands标志控制的)。

MyNavigationController.h

 #import  @interface MyNavigationController : UINavigationController @property(nonatomic, assign) BOOL acceptConflictingCommands; @end 

MyNavigationController.m

 #import "MyNavigationController.h" @interface MyNavigationController () @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,您必须以相同的方式覆盖它们以确保它们不会制动导航堆栈。