I have a presentation controller representing another presentation controller with modalPresentationStyle = UIModalPresentationCustom . Everything is set up so that part of the presentation of the presentation of the presentation of the presentation is displayed below the presented view of the view controller. In this state, the view controller of the view still performs automatic rotation correctly, and I handle the rotation for the presented view controller with automatic shutdown.
Now I'm trying to implement the interactive rejection of the presented view controller using the API to go to the iOS 7 user view. This works, except that when the interactive dismissal is canceled, the auto-rotation processing stops working. (It works again after the presented view controller is fired later.) Why is this happening, and how can I fix it?
EDIT . Here is the code you can run to demonstrate the problem. The view appears from below, and you can drop it by pulling it out. If you cancel the dismissal without missing it completely, the view controller view stops responding to rotations, and the presented view controller view has a corrupted layout.
EDIT . Here is the link to the code below as an Xcode project: https://drive.google.com/file/d/0BwcBqUuDfCG2YlhVWE1QekhUWlk/edit?usp=sharing
Sorry for the massive code reset, but I don't know what I'm doing wrong. Here's a sketch of what is happening: ViewController1 represents ViewController2 . ViewController1 implements UIViewControllerTransitioningDelegate , so it returns animated / interactive controllers for transitions. ViewController2 has a gesture recognizer that controls interactive firing; it implements UIViewControllerInteractiveTransitioning for use as an interactive controller for firing. It also holds a link to the firing animation controller to complete the transition if the user drags the view too much. Finally, there are two animation controller objects. PresentAnimationController sets auto-detection limits for cornering for the presented view controller view, and DismissAnimationController completes the dismissal.
ViewController1.h
#import <UIKit/UIKit.h> @interface ViewController1 : UIViewController <UIViewControllerTransitioningDelegate> @end
ViewController1.m
#import "ViewController1.h" #import "ViewController2.h" #import "PresentAnimationController.h" #import "DismissAnimationController.h" @implementation ViewController1 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.title = @"View 1"; self.navigationItem.prompt = @"Press "Present" and then swipe down to dismiss."; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Present" style:UIBarButtonItemStylePlain target:self action:@selector(pressedPresentButton:)]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; // Some subview just to check if layout is working. UIView * someSubview = [[UIView alloc] initWithFrame:self.view.bounds]; someSubview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; someSubview.backgroundColor = [UIColor orangeColor]; someSubview.layer.borderColor = [UIColor redColor].CGColor; someSubview.layer.borderWidth = 2; [self.view addSubview:someSubview]; } // -------------------- - (void)pressedPresentButton:(id)sender { ViewController2 * presentedVC = [[ViewController2 alloc] initWithNibName:nil bundle:nil]; presentedVC.modalPresentationStyle = UIModalPresentationCustom; presentedVC.transitioningDelegate = self; [self presentViewController:presentedVC animated:YES completion:nil]; } // -------------------- // View Controller Transitioning Delegate Methods. - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[PresentAnimationController alloc] init];; } - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { DismissAnimationController * animationController = [[DismissAnimationController alloc] init]; ViewController2 * presentedVC = (ViewController2 *)self.presentedViewController; if (presentedVC.dismissalIsInteractive) { presentedVC.dismissAnimationController = animationController; } return animationController; } - (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator { return nil; } - (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator { ViewController2 * presentedVC = (ViewController2 *)self.presentedViewController; if (presentedVC.dismissalIsInteractive) { return presentedVC; } else { return nil; } } @end
ViewController2.h
ViewController2.m
#import "ViewController2.h" @interface ViewController2 () @property (strong, nonatomic) id<UIViewControllerContextTransitioning> transitionContext; @end @implementation ViewController2 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { _dismissalIsInteractive = NO; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; // Set up content view. CGRect frame = UIEdgeInsetsInsetRect(self.view.bounds, UIEdgeInsetsMake(15, 15, 15, 15)); UIView * contentView = [[UIView alloc] initWithFrame:frame]; self.contentView = contentView; contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; contentView.backgroundColor = [UIColor cyanColor]; contentView.layer.borderColor = [UIColor blueColor].CGColor; contentView.layer.borderWidth = 2; [self.view addSubview:contentView]; // Set up pan dismissal gesture recognizer. UIPanGestureRecognizer * panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dismissalPan:)]; [self.view addGestureRecognizer:panGesture]; } // -------------------- - (void)dismissalPan:(UIPanGestureRecognizer *)panGesture { switch (panGesture.state) { case UIGestureRecognizerStateBegan: { _dismissalIsInteractive = YES; [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; break; } case UIGestureRecognizerStateChanged: { CGPoint translation = [panGesture translationInView:self.view]; CGFloat percent; if (translation.y > 0) { percent = translation.y / self.view.bounds.size.height; percent = MIN(percent, 1.0); } else { percent = 0; } // Swiping content view down. CGPoint center; center.x = CGRectGetMidX(self.view.bounds); center.y = CGRectGetMidY(self.view.bounds); if (translation.y > 0) { center.y += translation.y; // Only allow swiping down. } self.contentView.center = center; self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:(0.5 * (1.0 - percent))]; [self.transitionContext updateInteractiveTransition:percent]; break; } case UIGestureRecognizerStateEnded: // Fall through. case UIGestureRecognizerStateCancelled: { _dismissalIsInteractive = NO; id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext; self.transitionContext = nil; DismissAnimationController * dismissAnimationController = self.dismissAnimationController; self.dismissAnimationController = nil; CGPoint translation = [panGesture translationInView:self.view]; if (translation.y > 100) { // Complete dismissal. [dismissAnimationController animateTransition:transitionContext]; } else { // Cancel dismissal. void (^animations)() = ^() { CGPoint center; center.x = CGRectGetMidX(self.view.bounds); center.y = CGRectGetMidY(self.view.bounds); self.contentView.center = center; self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; }; void (^completion)(BOOL) = ^(BOOL finished) { [transitionContext cancelInteractiveTransition]; [transitionContext completeTransition:NO]; }; [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:animations completion:completion]; } break; } default: { break; } } } // -------------------- // View Controller Interactive Transitioning Methods. - (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext { self.transitionContext = transitionContext; } @end
PresentAnimationController.h
#import <Foundation/Foundation.h> @interface PresentAnimationController : NSObject <UIViewControllerAnimatedTransitioning> @end
PresentAnimationController.m
#import "PresentAnimationController.h" #import "ViewController2.h" @implementation PresentAnimationController - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; ViewController2 * toVC = (ViewController2 *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView * containerView = [transitionContext containerView]; CGPoint toCenter = fromVC.view.center; CGRect toBounds = fromVC.view.bounds; toVC.view.center = toCenter; toVC.view.bounds = toBounds; [toVC.view layoutIfNeeded]; [containerView addSubview:fromVC.view]; [containerView addSubview:toVC.view]; CGRect contentViewEndFrame = toVC.contentView.frame; CGRect contentViewStartFrame = contentViewEndFrame; contentViewStartFrame.origin.y += contentViewStartFrame.size.height; toVC.contentView.frame = contentViewStartFrame; UIColor * endBackgroundColor = toVC.view.backgroundColor; toVC.view.backgroundColor = [UIColor clearColor]; void (^animations)() = ^() { toVC.contentView.frame = contentViewEndFrame; toVC.view.backgroundColor = endBackgroundColor; }; void (^completion)(BOOL) = ^(BOOL finished) { toVC.view.autoresizingMask = UIViewAutoresizingNone; toVC.view.translatesAutoresizingMaskIntoConstraints = NO; NSLayoutConstraint * centerXConstraint = [NSLayoutConstraint constraintWithItem:toVC.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:fromVC.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]; NSLayoutConstraint * centerYConstraint = [NSLayoutConstraint constraintWithItem:toVC.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:fromVC.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]; NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:toVC.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:fromVC.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; NSLayoutConstraint * heightConstraint = [NSLayoutConstraint constraintWithItem:toVC.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:fromVC.view attribute:NSLayoutAttributeHeight multiplier:1 constant:0]; [containerView addConstraint:centerXConstraint]; [containerView addConstraint:centerYConstraint]; [containerView addConstraint:widthConstraint]; [containerView addConstraint:heightConstraint]; [transitionContext completeTransition:YES]; }; [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:animations completion:completion]; } - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } @end
DismissAnimationController.h
#import <Foundation/Foundation.h> @interface DismissAnimationController : NSObject <UIViewControllerAnimatedTransitioning> @end
DismissAnimationController.m
#import "DismissAnimationController.h" #import "ViewController2.h" @implementation DismissAnimationController - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { ViewController2 * fromVC = (ViewController2 *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController * toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView * containerView = [transitionContext containerView]; [containerView addSubview:toVC.view]; [containerView addSubview:fromVC.view]; void (^animations)() = ^() { CGRect contentViewEndFrame = fromVC.contentView.frame; contentViewEndFrame.origin.y = CGRectGetMaxY(fromVC.view.bounds) + 15; fromVC.contentView.frame = contentViewEndFrame; fromVC.view.backgroundColor = [UIColor clearColor]; }; void (^completion)(BOOL) = ^(BOOL finished) { if ([transitionContext isInteractive]) { [transitionContext finishInteractiveTransition]; } [transitionContext completeTransition:YES]; }; [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:animations completion:completion]; } - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } @end
AppDelegate.m
#import "AppDelegate.h" #import "ViewController1.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; ViewController1 * vc = [[ViewController1 alloc] initWithNibName:nil bundle:nil]; UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:vc]; self.window.rootViewController = nav; [self.window makeKeyAndVisible]; return YES; } @end