问题
So I have a UINavigationController, with Controller A as the root controller.
When I want to push Controller B on top, I want to use a custom animated transition and a custom interactive transition. This works fine.
When I want to push Controller C on top, I want to fall back to the default push/pop transitions that comes with UINavigationController. To make this happen I return nil for
navigationController:animationControllerForOperation:fromViewController:toViewController:
however if you return nil, then
navigationController:interactionControllerForAnimationController:
will never be called and the default "pan from left edge" pop interactive transition doesn't work.
Is there a way to return the default push/pop animation controller and interaction controller?
(Are there concrete implementations of id<UIViewControllerAnimatedTransitioning>
and id<UIViewControllerInteractiveTransitioning>
?)
Or some other way?
回答1:
You should set NavigationController's interactivePopGestureRecognizer delegate to self and then handle its behavior in -gestureRecognizerShouldBegin:
That is, when you want the built-in pop gesture to fire, you must return YES from this method. The same goes for your custom gestures - you have to figure out which recognizer you are dealing with.
- (void)setup
{
self.interactiveGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTransitionGesture:)];
self.interactiveGestureRecognizer.delegate = self;
[self.navigationController.view addGestureRecognizer:self.interactiveGestureRecognizer];
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// Don't handle gestures if navigation controller is still animating a transition
if ([self.navigationController.transitionCoordinator isAnimated])
return NO;
if (self.navigationController.viewControllers.count < 2)
return NO;
UIViewController *fromVC = self.navigationController.viewControllers[self.navigationController.viewControllers.count-1];
UIViewController *toVC = self.navigationController.viewControllers[self.navigationController.viewControllers.count-2];
if ([fromVC isKindOfClass:[ViewControllerB class]] && [toVC isKindOfClass:[ViewControllerA class]])
{
if (gestureRecognizer == self.interactiveGestureRecognizer)
return YES;
}
else if (gestureRecognizer == self.navigationController.interactivePopGestureRecognizer)
{
return YES;
}
return NO;
}
You can check out a sample project for your scenario. Transitions between view controllers A and B are custom animated, with custom B->A pop gesture. Transitions between view controllers B and C are default, with built-in navigation controller's pop gesture.
Hope this helps!
回答2:
You'll need to set the delegate every time before you present - in prepareForSeque: for example. If you want the custom transition, set it to self. If you want the default transition (like the default pop transition) set it to nil.
回答3:
Setting the delegate before/after each transition is a valid workaround, but if you implement other UINavigationControllerDelegate
's methods and you need to keep them, you can either have 2 delegate objects as suggested by Ziconic or play with NSObject's respondsToSelector:
. In your navigation delegate you could implement:
- (BOOL)respondsToSelector:(SEL)aSelector
{
if (aSelector == @selector(navigationController:animationControllerForOperation:fromViewController:toViewController:) ||
aSelector == @selector(navigationController:interactionControllerForAnimationController:)) {
return self.interactivePushTransitionEnabled;
}
return [super respondsToSelector:aSelector];
}
You should then make sure to update interactivePushTransitionEnabled
as needed. In your example, you should set the property to YES
only when controller A is being displayed.
There is only one more thing to do: force UINavigationController to reevaluate the methods implemented by its delegate. This can be easily done by doing something like this:
navigationController.delegate = nil;
navigationController.delegate = self; // or whatever object you use as the delegate
回答4:
try setting (self.)navigationController.delegate
to nil
(or return it to a previous value) after you've done your custom transition.
回答5:
in your gesture recognizer, set the delegate (to yourself) there and return your animated and interactive transitions. when returning the interactive transition, be sure to unset the delegate again so that everything else continues to use the default transitions.
I have a working example on github.
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
// Unset the delegate so that all other types of transitions (back, normal animated push not initiated by a gesture) get the default behavior.
self.navigationController.delegate = nil;
if (self.edgePanGestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.percentDrivenInteractiveTransition = [UIPercentDrivenInteractiveTransition new];
} else {
self.percentDrivenInteractiveTransition = nil;
}
return self.percentDrivenInteractiveTransition;
}
来源:https://stackoverflow.com/questions/20113701/ios-7-use-custom-interactive-transitions-only-some-of-the-time