简体   繁体   English

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

[英]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;
}

For more info check this link 有关更多信息,请查看此链接

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+。


Problem description 问题描述

  1. Application key window have UINavigationController's subclass as its rootViewController. 应用程序键窗口将UINavigationController的子类作为其rootViewController。
  2. This NC subclass prohibits some of the interface orientations. 该NC子类禁止某些界面方向。
  3. Some View Controller (VC1) in the NC stack is presenting another View Controller (VC2) modally and fullscreen. NC堆栈中的某些视图控制器(VC1)以模态和全屏方式呈现另一个视图控制器(VC2)。
  4. This presented VC2 allows more interface orientations than NC do. 这个呈现的VC2允许比NC更多的接口方向。
  5. User rotates device to orientation that is prohibited by NC, but allowed by presented VC2. 用户将设备旋转到NC禁止的方向,但是由VC2允许。
  6. User dismisses the presented VC2. 用户驳回提供的VC2。
  7. View of VC1 has incorrect frame. VC1的视图具有不正确的帧。

Setup and illustration 设置和插图

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堆栈保持在横向状态:

VC2被解雇了


Cause 原因

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堆栈时存在一个错误。


Solution

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.

相关问题 如何在iOS 8中的模态视图控制器中强制纵向方向? - How do I force portrait orientation in a modal view controller in iOS 8? 模态视图控制器强制iOS 6中的横向方向 - Modal View Controller force Landscape orientation in iOS 6 强制iOS方向在一个模态视图控制器上横向移动 - Force iOS orientation to landscape on one modal view controller iOS 9中的强制视图控制器方向 - Force View Controller Orientation in iOS 9 iPhone iOS 6 UITabBarController仅限于纵向,如何使Modal View Controller支持横向方向 - iPhone iOS 6 UITabBarController Portrait Only, How to have Modal View Controller support Landscape Orientation 除模态视图控制器外的所有视图控制器中的纵向方向 - Portrait orientation in all view controllers except in modal view controller 如何以纵向方向关闭UISplitController的模态表示 - how to dismiss a modal presentation of UISplitController in Portrait orientation 强制纵向 IOS (swift) - Force portrait orientation IOS (swift) 如何在iOS 6中强制将方向设为纵向 - How to force Orientation as Portrait in iOS 6 ios:如何解除模态视图控制器,然后弹出推送视图控制器 - ios: how to dismiss a modal view controller and then pop a pushed view controller
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM