I've been dabbling with the new iOS 7 custom transition API and looked through all the tutorials/documentation I could find but I can't seem to figure this stuff out for my specific scenario.
So essentially what I'm trying to implement is a UIPanGestureRecognizer on a view where I would swipe up and transition to a VC whose view would slide up from the bottom while the current view would slide up as I drag my finger higher.
I have no problem accomplishing this without the interaction transition, but once I implement the interaction (the pan gesture) I can't seem to complete the transition.
Here's the relevant code from the VC that conforms to the UIViewControllerTransitionDelegate which is needed to vend the animator controllers:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"Swipe"]) {
NSLog(@"PREPARE FOR SEGUE METHOD CALLED");
UIViewController *toVC = segue.destinationViewController;
[interactionController wireToViewController:toVC];
toVC.transitioningDelegate = self;
toVC.modalPresentationStyle = UIModalPresentationCustom;
}
}
#pragma mark UIViewControllerTransition Delegate Methods
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController: (UIViewController *)presented presentingController: (UIViewController *)presenting sourceController:(UIViewController *)source {
NSLog(@"PRESENTING ANIMATION CONTROLLER CALLED");
SwipeDownPresentationAnimationController *transitionController = [SwipeDownPresentationAnimationController new];
return transitionController;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
NSLog(@"DISMISS ANIMATION CONTROLLER CALLED");
DismissAnimatorViewController *transitionController = [DismissAnimatorViewController new];
return transitionController;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator {
NSLog(@"Interaction controller for dimiss method caled");
return interactionController.interactionInProgress ? interactionController:nil;
}
NOTE: The interaction swipe is only for the dismissal of the VC which is why it's in the interactionControllerForDismissal method
Here's the code for the animator of the dismissal which works fine when I tap on a button to dismiss it:
#import "DismissAnimatorViewController.h"
@implementation DismissAnimatorViewController
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 1.0;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
NSTimeInterval duration = [self transitionDuration:transitionContext];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
CGRect initialFrameFromVC = [transitionContext initialFrameForViewController:fromVC];
UIView *containerView = [transitionContext containerView];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
NSLog(@"The screen bounds is :%@", NSStringFromCGRect(screenBounds));
toVC.view.frame = CGRectOffset(initialFrameFromVC, 0, screenBounds.size.height);
toVC.view.alpha = 0.2;
CGRect pushedPresentingFrame = CGRectOffset(initialFrameFromVC, 0, -screenBounds.size.height);
[containerView addSubview:toVC.view];
[UIView animateWithDuration:duration
delay:0
usingSpringWithDamping:0.6
initialSpringVelocity:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
fromVC.view.frame = pushedPresentingFrame;
fromVC.view.alpha = 0.2;
toVC.view.frame = initialFrameFromVC;
toVC.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
@end
Here's the code for the UIPercentDrivenInteractiveTransition subclass which serves as the interaction controller:
#import "SwipeInteractionController.h"
@implementation SwipeInteractionController {
BOOL _shouldCompleteTransition;
UIViewController *_viewController;
}
- (void)wireToViewController:(UIViewController *)viewController {
_viewController = viewController;
[self prepareGestureRecognizerInView:_viewController.view];
}
- (void)prepareGestureRecognizerInView:(UIView*)view {
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
gesture.minimumNumberOfTouches = 1.0;
[view addGestureRecognizer:gesture];
}
- (CGFloat)completionSpeed {
return 1 - self.percentComplete;
NSLog(@"PERCENT COMPLETE:%f",self.percentComplete);
}
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer {
// CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
// 1. Start an interactive transition!
self.interactionInProgress = YES;
[_viewController dismissViewControllerAnimated:YES completion:nil];
break;
case UIGestureRecognizerStateChanged: {
// 2. compute the current position
CGFloat fraction = fabsf(translation.y / 568);
NSLog(@"Fraction is %f",fraction);
fraction = fminf(fraction, 1.0);
fraction = fmaxf(fraction, 0.0);
// 3. should we complete?
_shouldCompleteTransition = (fraction > 0.23);
// 4. update the animation controller
[self updateInteractiveTransition:fraction];
NSLog(@"Percent complete:%f",self.percentComplete);
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
// 5. finish or cancel
NSLog(@"UI GESTURE RECOGNIZER STATE CANCELED");
self.interactionInProgress = NO;
if (!_shouldCompleteTransition || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];
NSLog(@"Interactive Transition is cancled.");
}
else {
NSLog(@"Interactive Transition is FINISHED");
[self finishInteractiveTransition];
}
break;
}
default:
NSLog(@"Default is being called");
break;
}
}
@end
Once again, when I run the code now and I don't swipe all the way to purposefully cancel the transition, I just get a flash and am presented with the view controller I want to swipe to. This happens regardless if the transition completes or is canceled.
However, when I dismiss via the button I get the transition specified in my animator view controller.
UIPercentDrivenInteractiveTransition
methods for beginning, ending, and updating the animation. I don't see them implemented anywhere in your code. – Ash FurrowUIPercentDrivenInteractiveTransition
methods, but what I don't get is the reason for it. Is it necessary? I'm currently in the middle of an implementation without it, and admittedly I am getting some weird behaviour (e.g. finishing animations repeat again from the percentage at where the gesture ended after initial finish, screen going black after interactive cancel), but I'm not 100% sure that's the cause. Anyhoo, just wondering if you had a reason for the overrides. Thanks! – YazidUIPercentDrivenInteractiveTransition
is not strictly necessary, but it's the easiest and "Apple friendly" approach. – Ash Furrow