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

时间:2014-11-24 18:45:56

标签: ios objective-c iphone crash viewcontroller

我看到的唯一解决方案是回答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

iOS app error - Can't add self as subview

3 个答案:

答案 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

上一个回答:

  

上一个答案的问题:isBeingPresentedisBeingDismissed仅适用于viewDidLoad:viewDidApper:

虽然我自己没有对此进行测试,但这是一个建议。

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

NSArray *viewControllers = self.navigationController.viewControllers;

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

幸运的是,在iOS 5中引入了两种特别方便的方法:isBeingPresentedisBeingDismissed返回&#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,对于方法混合代码,积分转到NSHipsterThis 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,您必须以相同的方式覆盖它们,以确保它们不会制动导航堆栈。