这个解决方案适用于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视图堆栈:
![初始外观](https://istack.dev59.com/h5rbR.webp)
从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已呈现并设备旋转为横向:
![Presented and rotated](https://istack.dev59.com/DY7D1.webp)
VC2已解除,设备回到纵向模式,但NC堆栈仍处于横向模式:
![VC2 dismissed](https://istack.dev59.com/CaiVe.webp)
原因
苹果文档指出:
当您使用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;
@interface BTPresentedAC : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation BTPresentedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect frame = [[[UIApplication sharedApplication] keyWindow] bounds];
toVC.view.frame = CGRectMake(frame.origin.x, frame.origin.y + frame.size.height, frame.size.width, frame.size.height);
[[context containerView] addSubview:toVC.view];
UIViewAnimationOptions options = (UIViewAnimationOptionCurveEaseOut);
[UIView animateWithDuration:kDuration delay:0 options:options animations:^{
toVC.view.frame = frame;
} completion:^(BOOL finished) {
[context completeTransition:finished];
}];
}
@end
@interface BTDismissedAC : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation BTDismissedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
toVC.view.frame = [[[UIApplication sharedApplication] keyWindow] bounds];
[[context containerView] insertSubview:toVC.view belowSubview:fromVC.view];
CGRect frame = fromVC.view.frame;
CGAffineTransform transform = fromVC.view.transform;
if (transform.b == -1) {
frame = CGRectMake(frame.origin.x + frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.b == 1) {
frame = CGRectMake(frame.origin.x - frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.a == -1) {
frame = CGRectMake(frame.origin.x, frame.origin.y - frame.size.height, frame.size.width, frame.size.height);
} else {
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:^{
fromVC.view.frame = frame;
} completion:^(BOOL finished) {
[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:
存储一个实例的强引用:
@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]