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+。
问题描述
- 应用程序密钥窗口将UINavigationController的子类作为其rootViewController。
- 该NC子类禁止某些接口方向。
- NC堆栈中的某些视图控制器(VC1)以模态和全屏方式呈现另一个视图控制器(VC2)。
- 这个呈现的VC2允许比NC更多的接口方向。
- 用户将设备旋转到NC禁止的方向,但是由VC2允许。
- 用户驳回呈现的VC2。
- 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堆栈保持在横向状态:
原因
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];