2014-11-24 21 views
1

我看到的唯一解決方案是對一個計算器問題的回答。我發佈了下面的鏈接。我指的是第五個答案。似乎有些用戶在解決方案方面存在一些問題。我不知道是否有另一個類別可以阻止兩個控制器同時被推送。任何提示或建議表示讚賞。iOS:目前是否有辦法阻止兩個視圖控制器同時被推入或彈出?

#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

+0

只要你使用導航堆棧,看起來好像你應該能夠確認視圖控制器在彈出之前是堆棧中的最後一個,並且在推送之前它是在它的預期前輩之前。 – 2014-11-24 19:34:12

回答

2

更新答案:

我喜歡這個解決方案由nonamelive在Github上什麼,我原貼:https://gist.github.com/nonamelive/9334458。通過繼承UINavigationController並利用UINavigationControllerDelegate,您可以確定發生轉換的時間,防止在轉換期間發生其他轉換,並且在同一個類中進行轉換。下面是其中不包括私有API nonamelive的解決方案的更新:

#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中引入了兩個特別方便的方法:和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. 

實際上,你可能不會通過堆棧上的每個視圖控制器有循環,但也許這個建議將幫助你動身的右腳。

+0

我在測試之前投了票,但這實際上不起作用。如果在'pushViewController:animated:','isBeingPresented'和'isBeingDismissed'之後調用返回false ... – deadbeef 2015-04-13 14:53:04

+0

@deadbeef該文檔明確聲明isBeingPresented「返回一個布爾值,指示視圖控制器是否處於呈現過程中由其祖先之一。「並且如果它正在呈現過程中應該返回true ...您能分享您的特定代碼嗎?也許這是一個線程問題... – 2015-04-13 15:59:53

+0

我剛剛發現了原因。該文檔還說:「只有在'viewWillAppear:'和'viewDidAppear:'methods」內部調用時,此方法纔會返回YES。對於我們在這裏要做的事情不太實際。我最終實現了類似於這樣的東西:https://gist.github.com/nonamelive/9334458使用方法swizzling而不是子類化。 – deadbeef 2015-04-13 16:08:09

1

這裏是我的方法,使用UINavigationController類別和方法swizzling。 方法-[UINavigationController didShowViewController:animated:]是私人的,所以雖然已經報告使用安全,但使用您自己的風險。

對於想法和NSHipster積分轉到this answer爲方法swizzling代碼。 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 
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,您必須以相同方式覆蓋它們以確保它們不會制動導航堆棧。

+0

謝謝你,這工作得很好。 – 2015-06-06 14:16:08

+0

似乎有一個錯誤。當用戶通過使用邊緣手勢滑動來彈出當前vc,然後在移動過程中取消該手勢時,「_shouldIgnoreStackRequests」被設置爲「YES」並且永不回退。 – 2015-07-29 05:32:28

+0

@ hris.to調用popToRootViewController:animated:safe或者它也應該反映在這段代碼中? – 2017-07-13 12:53:18

相關問題