[英]iOS 7+ Dismiss Modal View Controller and Force Portrait Orientation
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 with a cross-dissolve presentation style. 我有一个UINavigationController作为我在iOS 7和iOS 8上的UIWindow的根视图控制器。从它的一个视图控制器,我提出了一个具有交叉溶解演示风格的全屏模态视图控制器。 This modal view controller should be able to rotate to all orientations, and it works fine.
这个模态视图控制器应该能够旋转到所有方向,并且它工作正常。
The problem is when the device is held in a landscape orientation and the modal view controller is dismissed. 问题是当设备以横向方向保持并且模态视图控制器被解除时。 The view controller which presented the modal only supports portrait orientation, and I've confirmed that UIInterfaceOrientationMaskPortrait is returned to -application:supportedInterfaceOrientationsForWindow:.
呈现模态的视图控制器仅支持纵向方向,并且我已确认UIInterfaceOrientationMaskPortrait返回到-application:supportedInterfaceOrientationsForWindow:。 -shouldAutorotate returns YES, as well.
-shouldAutorotate也返回YES。 However, the orientation of the presenting view controller, after dismissing the modal, remains landscape.
然而,在解除模态之后,呈现视图控制器的方向仍然是风景。 How can I force it to remain in portrait orientation while allowing the modal to take the orientation of the device?
在允许模态采取设备方向的同时,如何强制它保持纵向方向? My code follows:
我的代码如下:
App delegate: 应用代表:
- (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);
}
Presenting view controller: 呈现视图控制器:
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
Modal view controller: 模态视图控制器:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskPortrait);
}
If the modal controller was in landscape orientation before dismissal, the presenting ViewController may not return to the origin orientation (portrait). 如果模态控制器在解除之前处于横向方向,则呈现的ViewController可能不会返回到原点方向(纵向)。 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.
问题是因为在控制器实际被解除之前调用了AppDelegate supportedInterfaceOrientationsForWindow方法,并且呈现的控制器检查仍然返回Landscape掩码。
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. 并且在模式中呈现的ViewController根据标志设置方向:当呈现模态ViewController时 - 返回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. 最后一步 - 从你的AppDelegate调用模态呈现ViewController的方向。 I am just checking the currently presented ViewController and call the supportedInterfaceOrientations on it
我只是检查当前呈现的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;
}
I ended up subclassing the UINavigationController and overriding its rotation methods. 我最终继承了UINavigationController并重写了它的旋转方法。 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.
以下解决方案适用于iOS 7,但我相信iOS 8 beta 5中存在一个错误,导致视图控制器的视图在以横向方向解除模式后缩小到屏幕高度的一半。
UINavigationController subclass: UINavigationController子类:
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
This solution is for iOS 8+. 此解决方案适用于iOS 8+。
UINavigationController's subclass: UINavigationController的子类:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutorotate
{
return YES;
}
VC1 initial appearance and UI view stack: VC1初始外观和UI视图堆栈:
Presenting VC2 (QLPreviewController in that example) from VC1: 从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 is presented and device rotated to landscape: 显示VC2并将设备旋转为横向:
VC2 dismissed, device is back in portrait mode, but NC stack remains in landscape: VC2解除,设备返回纵向模式,但NC堆栈保持在横向状态:
Apple documentation states: Apple文档说明:
When you present a view controller using the presentViewController:animated:completion: method, UIKit always manages the presentation process.
当您使用presentViewController:animated:completion:方法呈现视图控制器时,UIKit始终管理演示过程。 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. 显然在处理UINavigationController堆栈时存在一个错误。
This bug can be bypassed by providing our own transitioning delegate. 通过提供我们自己的转换委托可以绕过此错误。
BTTransitioningDelegate.h BTTransitioningDelegate.h
#import <UIKit/UIKit.h>
@interface BTTransitioningDelegate : NSObject <UIViewControllerTransitioningDelegate>
@end
BTTransitioningDelegate.m 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: 在呈现VC时导入转换委托:
#import "BTTransitioningDelegate.h"
Store a strong reference to an instance: 存储对实例的强引用:
@property (nonatomic, strong) BTTransitioningDelegate *transitioningDelegate;
Instantiate in -viewDidLoad
: 在
-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];
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.