簡體   English   中英

iOS 7+關閉模態視圖控制器和強制縱向方向

[英]iOS 7+ Dismiss Modal View Controller and Force Portrait Orientation

我有一個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;
}

有關更多信息,請查看此鏈接

我最終繼承了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 <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

在呈現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];

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM