2
votes

iOS 7 has a great interactive animation for popping UIViewControllers. The transition is triggered by swiping from the left side of the screen but I would like to trigger the transition by swiping anywhere in my view controller. (I would also like to cancel the ones from the edge so I can use them for another custom transition...).

So far in my view controller I've added this in init. I know it's wrong, I'm not sure what I'm doing really.

UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
panRecognizer.delegate = self.navigationController.interactivePopGestureRecognizer.delegate;
[self.view addGestureRecognizer:panRecognizer];

How do I tie it to the built in interactivePopGestureRecognizer? Should that be done in my handleGesture: method?

Edit: In Apple's documentation the word tie is actually used:

interactivePopGestureRecognizer

The navigation controller installs this gesture recognizer on its view and uses it to pop the topmost view controller off the navigation stack. You can use this property to retrieve the gesture recognizer and tie it to the behavior of other gesture recognizers in your user interface. When tying your gesture recognizers together, make sure they recognize their gestures simultaneously to ensure that your gesture recognizers are given a chance to handle the event.

How do you tie to UIGestureRecognizers together?

3

3 Answers

1
votes

I think what the documentation means is that you can use that accessor to retrieve the navigation controller’s interactivePopGestureRecognizer for use in your own recognizers’ delegate callbacks. For example, in -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:, you might want to return YES only if otherGestureRecognizer is the view controller’s interactive pop gesture recognizer.

5
votes

Why interactivePopGestureRecognizer is worthless for this case

Gesture recognizers have two notification channels, the delegate and the target/action. The delegate is used for "should I even consider accepting this gesture" and this one does internal checks like:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    return !self.isAnimating &&
           self.viewControllers.count > 1 &&
           !self.navigationBarHidden &&
           [self _doesTheNavigationControllerDelegateHaveCustomTransitions] &&
           [self _someOtherSecretJunk];
}

If the tests pass, it then calls the target/action which actually sets up and animates the interactive transition.

The only way to get the built-in pop interaction is for interactivePopGestureRecognizer to pass its delegate tests and call the action. If knew what the target/action were, we could try hooking that to your custom gesture like this:

[[UIPanGestureRecognizer alloc] 
    initWithTarget:self.navigationController._secretGestureRecognizerTarget 
            action:@selector(_secretGestureRecognizerAction:)];

But even then, Apple could have undocumented checks like:

- (void)_secretGestureRecognizerAction:(id)sender
{
    NSParameterAssert(sender == self.navigationController.interactiveGestureRecognizer);
    ...
}

What you can do

What you can do is a lot more complicated than you were probably hoping but has a lot more potential. You need to create your own interactive transition and attach it to your own gesture recognizer.

You're going to want to read tutorials and look at sample code on the subject, but the basic steps are:

  1. Create a subclass of UIPercentDrivenInteractiveTransition that conforms to UIViewControllerAnimatedTransitioning
  2. Implement animateTransition: in a way that matches the appearance of the default transition (or your own design).
  3. Create a class that conforms to UINavigationControllerDelegate and implement the animation methods as well as a method for handling the gesture recognizer.
  4. Set up the navigation controller to use that delegate, add the gesture recognizer to your view controller with that class handling target/action and delegate methods.

It's important to note that once you add your own custom transitions, interactivePopGestureRecognizer effectively dies and the standard interactions cease to work so you'll need to do your custom stuff everywhere.

1
votes

What I've done is this:

[[_myCustomRecogniser mutableArrayValueForKey:@"targets"] addObjectsFromArray:[self.navigationController.interactivePopGestureRecognizer valueForKey:@"targets"]];

Whether or not Apple approves of such a use of KVC in App Store apps remains to be seen. It does work, however, so apparently no undocumented runtime checking is taking place.