iOS 7+ Dismiss Modal View Controller and Force Portrait Orientation

前端 未结 3 1201
时光说笑
时光说笑 2021-02-05 14:13

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 wit

3条回答
  •  北海茫月
    2021-02-05 14:47

    This solution is for iOS 8+.


    Problem description

    1. Application key window have UINavigationController's subclass as its rootViewController.
    2. This NC subclass prohibits some of the interface orientations.
    3. Some View Controller (VC1) in the NC stack is presenting another View Controller (VC2) modally and fullscreen.
    4. This presented VC2 allows more interface orientations than NC do.
    5. User rotates device to orientation that is prohibited by NC, but allowed by presented VC2.
    6. User dismisses the presented VC2.
    7. View of VC1 has incorrect frame.

    Setup and illustration

    UINavigationController's subclass:

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

    VC1 initial appearance and UI view stack:

    Initial appearance

    Presenting VC2 (QLPreviewController in that example) from VC1:

    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:

    Presented and rotated

    VC2 dismissed, device is back in portrait mode, but NC stack remains in landscape:

    VC2 dismissed


    Cause

    Apple documentation states:

    When you present a view controller using the presentViewController:animated:completion: method, UIKit always manages the presentation process. 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.


    Solution

    This bug can be bypassed by providing our own transitioning delegate.

    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
    

    Import that transitioning delegate in presenting VC:

    #import "BTTransitioningDelegate.h"
    

    Store a strong reference to an instance:

    @property (nonatomic, strong) BTTransitioningDelegate *transitioningDelegate;
    

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

提交回复
热议问题