I have a UINavigationController as the root view controller of my UIWindow on iOS 7 and iOS 8. From one of its view controllers, I present a fullscreen modal view controller wit
This solution is for iOS 8+.
UINavigationController's subclass:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutorotate
{
return YES;
}
VC1 initial appearance and UI view stack:
Presenting VC2 (QLPreviewController in that example) from VC1:
QLPreviewController *pc = [[QLPreviewController alloc] init];
pc.dataSource = self;
pc.delegate = self;
pc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:pc animated:YES completion:nil];
VC2 is presented and device rotated to landscape:
VC2 dismissed, device is back in portrait mode, but NC stack remains in landscape:
Apple documentation states:
When you present a view controller using the presentViewController:animated:completion: method, UIKit always manages the presentation process. Part of that process involves creating the presentation controller that is appropriate for the given presentation style.
Apparently there is a bug in handling UINavigationController stack.
This bug can be bypassed by providing our own transitioning delegate.
BTTransitioningDelegate.h
#import <UIKit/UIKit.h>
@interface BTTransitioningDelegate : NSObject <UIViewControllerTransitioningDelegate>
@end
BTTransitioningDelegate.m
#import "BTTransitioningDelegate.h"
static NSTimeInterval kDuration = 0.5;
// This class handles presentation phase.
@interface BTPresentedAC : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation BTPresentedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
// presented VC
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
// presented controller ought to be fullscreen
CGRect frame = [[[UIApplication sharedApplication] keyWindow] bounds];
// we will slide view of the presended VC from the bottom of the screen,
// so here we set the initial frame
toVC.view.frame = CGRectMake(frame.origin.x, frame.origin.y + frame.size.height, frame.size.width, frame.size.height);
// [context containerView] acts as the superview for the views involved in the transition
[[context containerView] addSubview:toVC.view];
UIViewAnimationOptions options = (UIViewAnimationOptionCurveEaseOut);
[UIView animateWithDuration:kDuration delay:0 options:options animations:^{
// slide view to position
toVC.view.frame = frame;
} completion:^(BOOL finished) {
// required to notify the system that the transition animation is done
[context completeTransition:finished];
}];
}
@end
// This class handles dismission phase.
@interface BTDismissedAC : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation BTDismissedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
// presented VC
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
// presenting VC
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
// inserting presenting VC's view under presented VC's view
toVC.view.frame = [[[UIApplication sharedApplication] keyWindow] bounds];
[[context containerView] insertSubview:toVC.view belowSubview:fromVC.view];
// current frame and transform of presented VC
CGRect frame = fromVC.view.frame;
CGAffineTransform transform = fromVC.view.transform;
// determine current presented VC's view rotation and assemble
// target frame to provide naturally-looking dismissal animation
if (transform.b == -1) {
// -pi/2
frame = CGRectMake(frame.origin.x + frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.b == 1) {
// pi/2
frame = CGRectMake(frame.origin.x - frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.a == -1) {
// pi
frame = CGRectMake(frame.origin.x, frame.origin.y - frame.size.height, frame.size.width, frame.size.height);
} else {
// 0
frame = CGRectMake(frame.origin.x, frame.origin.y + frame.size.height, frame.size.width, frame.size.height);
}
UIViewAnimationOptions options = (UIViewAnimationOptionCurveEaseOut);
[UIView animateWithDuration:kDuration delay:0 options:options animations:^{
// slide view off-screen
fromVC.view.frame = frame;
} completion:^(BOOL finished) {
// required to notify the system that the transition animation is done
[context completeTransition:finished];
}];
}
@end
@implementation BTTransitioningDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [[BTPresentedAC alloc] init];
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return [[BTDismissedAC alloc] init];
}
@end
Import that transitioning delegate in presenting VC:
#import "BTTransitioningDelegate.h"
Store a strong reference to an instance:
@property (nonatomic, strong) BTTransitioningDelegate *transitioningDelegate;
Instantiate in -viewDidLoad
:
self.transitioningDelegate = [[BTTransitioningDelegate alloc] init];
Call when appropriate:
QLPreviewController *pc = [[QLPreviewController alloc] init];
pc.dataSource = self;
pc.delegate = self;
pc.transitioningDelegate = self.transitioningDelegate;
pc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:pc animated:YES completion:nil];
I ended up subclassing the UINavigationController and overriding its rotation methods. The following solution works on iOS 7, but I believe there is a bug in iOS 8 beta 5 that causes the presenting view controller's view to shrink to half the screen-height after dismissing the modal in landscape orientation.
UINavigationController subclass:
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
If the modal controller was in landscape orientation before dismissal, the presenting ViewController may not return to the origin orientation (portrait). The problem is because the AppDelegate supportedInterfaceOrientationsForWindow method is called before the controller is actually dismissed and the presented controller check still returns Landscape mask.
Set a flag to indicate whether the (modal) presented view controller will be displayed or not.
- (void)awakeFromNib // or where you instantiate your ViewController from
{
[super awakeFromNib];
self.presented = YES;
}
- (IBAction)exitAction:(id)sender // where you dismiss the modal
{
self.presented = NO;
[self dismissViewControllerAnimated:NO completion:nil];
}
And in the modal presented ViewController set the orientation according to the flag: When the modal ViewController is presented - return Landscape. When it is dismissed then return portrait
- (NSUInteger)supportedInterfaceOrientations
{
if ([self isPresented]) {
return UIInterfaceOrientationMaskLandscape;
} else {
return UIInterfaceOrientationMaskPortrait;
}
}
Last step - from your AppDelegate call the modal presented ViewController for its orientation. I am just checking the currently presented ViewController and call the supportedInterfaceOrientations on it
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
NSUInteger orientationMask = UIInterfaceOrientationMaskPortrait;
UIViewController *currentVC = self.window.rootViewController.presentedViewController; // gets the presented VC
orientationMask = [currentVC supportedInterfaceOrientations];
return orientationMask;
}
For more info check this link