iOS 7+关闭模态视图控制器和强制纵向方向

我有一个UINavigationController作为我在iOS 7和iOS 8上的UIWindow的根视图控制器。从它的一个视图控制器,我提出了一个具有交叉溶解演示风格的全屏模态视图控制器。 这个模态视图控制器应该能够旋转到所有方向,并且它工作正常。

问题是当设备以横向方向保持并且模态视图控制器被解除时。 呈现模态的视图控制器仅支持纵向方向,并且我已确认UIInterfaceOrientationMaskPortrait返回到-application:supportedInterfaceOrientationsForWindow:。 -shouldAutorotate也返回YES。 然而,在解除模态之后,呈现视图控制器的方向仍然是风景。 在允许模态采取设备方向的同时,如何强制它保持纵向方向? 我的代码如下:

应用代表:

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { UINavigationController *navigationController = (UINavigationController *)self.deckController.centerController; NSArray *viewControllers = [navigationController viewControllers]; UIViewController *top = [viewControllers lastObject]; if (top && [top presentedViewController]) { UIViewController *presented = [top presentedViewController]; if ([presented respondsToSelector:@selector(isDismissing)] && ![(id)presented isDismissing]) { top = presented; } } return [top supportedInterfaceOrientations]; } return (UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskLandscapeRight); } 

呈现视图控制器:

 - (BOOL)shouldAutorotate { return YES; } - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { return UIInterfaceOrientationPortrait; } 

模态视图控制器:

 - (BOOL)shouldAutorotate { return YES; } - (NSUInteger)supportedInterfaceOrientations { return (UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskPortrait); } 

如果模态控制器在解除之前处于横向方向,则呈现的ViewController可能不会返回到原点方向(纵向)。 问题是因为在控制器实际被解除之前调用了AppDelegate supportedInterfaceOrientationsForWindow方法,并且呈现的控制器检查仍然返回Landscape掩码。

设置一个标志以指示是否显示(模态) 显示的视图控制器。

 - (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]; } 

并且在提供的模态中,ViewController根据标志设置方向:当呈现模态ViewController时 – 返回Landscape。 当它被解雇然后返回肖像

 - (NSUInteger)supportedInterfaceOrientations { if ([self isPresented]) { return UIInterfaceOrientationMaskLandscape; } else { return UIInterfaceOrientationMaskPortrait; } } 

最后一步 – 从你的AppDelegate调用模态呈现ViewController的方向。 我只是检查当前呈现的ViewController并在其上调用supportedInterfaceOrientations

 - (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; } 

有关更多信息,请查看此链接

我最终inheritance了UINavigationController并重写了它的旋转方法。 以下解决方案适用于iOS 7,但我相信iOS 8 beta 5中存在一个错误,导致视图控制器的视图在横向偏移模式后缩小到屏幕高度的一半。

UINavigationController子类:

 - (BOOL)shouldAutorotate { return NO; } - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { return UIInterfaceOrientationPortrait; } 

此解决方案适用于iOS 8+。


问题描述

  1. 应用程序密钥窗口将UINavigationController的子类作为其rootViewController。
  2. 该NC子类禁止某些接口方向。
  3. NC堆栈中的某些视图控制器(VC1)以模态和全屏方式呈现另一个视图控制器(VC2)。
  4. 这个呈现的VC2允许比NC更多的接口方向。
  5. 用户将设备旋转到NC禁止的方向,但是由VC2允许。
  6. 用户驳回呈现的VC2。
  7. VC1的视图具有不正确的帧。

设置和插图

UINavigationController的子类:

 - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait; } - (BOOL)shouldAutorotate { return YES; } 

VC1初始外观和UI视图堆栈:

初次亮相

从VC1呈现VC2(该示例中的QLPreviewController):

 QLPreviewController *pc = [[QLPreviewController alloc] init]; pc.dataSource = self; pc.delegate = self; pc.modalPresentationStyle = UIModalPresentationFullScreen; [self.navigationController presentViewController:pc animated:YES completion:nil]; 

显示VC2并将设备旋转为横向:

提出并轮换

VC2被解雇,设备返回纵向模式,但NC堆栈保持在横向状态:

VC2被解雇了


原因

Apple文档说明:

当您使用presentViewController:animated:completion:方法呈现视图控制器时,UIKit始终管理演示过程。 该过程的一部分涉及创建适合给定演示样式的演示控制器。

显然处理UINavigationController堆栈时存在一个错误。


通过提供我们自己的转换委托可以绕过此错误。

BTTransitioningDelegate.h

 #import  @interface BTTransitioningDelegate : NSObject  @end 

BTTransitioningDelegate.m

 #import "BTTransitioningDelegate.h" static NSTimeInterval kDuration = 0.5; // This class handles presentation phase. @interface BTPresentedAC : NSObject  @end @implementation BTPresentedAC - (NSTimeInterval)transitionDuration:(id )transitionContext { return kDuration; } - (void)animateTransition:(id)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  @end @implementation BTDismissedAC - (NSTimeInterval)transitionDuration:(id )transitionContext { return kDuration; } - (void)animateTransition:(id)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 )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[BTPresentedAC alloc] init]; } - (id )animationControllerForDismissedController:(UIViewController *)dismissed { return [[BTDismissedAC alloc] init]; } @end 

在呈现VC时导入转换委托:

 #import "BTTransitioningDelegate.h" 

存储对实例的强引用:

 @property (nonatomic, strong) BTTransitioningDelegate *transitioningDelegate; 

-viewDidLoad实例化:

 self.transitioningDelegate = [[BTTransitioningDelegate alloc] init]; 

适当时打电话:

 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];