此解决方案适用于 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 视图堆栈:
从 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 堆栈仍处于横向模式:
原因
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];