Morris_ 2019.04.11
前面有总结过一些在开发中遇到的屏幕旋转的基础知识。
一、设置应用支持的转屏方向
设置方式
00x1
在TARGET->General->Deployment Info->Device Orientations下,可设置App支持的屏幕旋转方向。
这里的设置会同步到应用的plist文件中。
00x2
苹果也提供了代码设置app支持的转屏方式的Api:
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
这个Api是UIApplicationDelegate中的一个方法,我们在AppDelegate.m中实现此协议方法即可。
区别比较
按照00x1设置后,如果代码中又使用了00x2,则00x2会覆盖00x1的设置。但是对于闪屏的方向设置,00x2的方式是无效的,00x1的设置方式确是有效的。可以理解为,应用在进入时,先读取了plist文件中的屏转设置,然后按照plist文件中的设置进行闪屏的展示,程序执行进AppDelegate.m中时,通过00x2中的Api又获取了一次应用所支持的屏转设置,所以闪屏支持的屏幕旋转设置,必须用00x1的方式设置,00x2的设置是无效的,进入应用后应用支持的转屏方式两种方式都可以。
小结
设置闪屏支持的转屏方式,只能使用00x1的方式,00x2的方式是无效的。
如果不使用代码,直接使用00x1的方式,是可行的。
如果既使用00x1的方式,也使用了代码设置,则00x1的方式设置了闪屏支持的旋转方向,00x2的方式设置了进入应用后界面支持的旋转方向。当然个别界面可以单独设置其支持的旋转方向。
二、设置某个界面支持的转屏方向
有必要了解一下苹果提供的UIViewController的分类UIViewControllerRotation。
// To make it more convenient for applications to adopt rotation, a view controller may implement the below methods. Your UIWindow's frame should use [UIScreen mainScreen].bounds as its frame.
@interface UIViewController (UIViewControllerRotation)
...略
- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
- (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
// Returns interface orientation masks.
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
...略
@end
需要注意一下我们经常用到的设置某个controller支持的屏转方向的三个方法。
- (BOOL)shouldAutorotate
{
return YES;//设置当前controller是否支持重力感应,即是否可以转屏
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAllButUpsideDown;//设置支持的转屏方向
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;//设置初始的屏幕方向
}
在iOS开关中屏幕旋转(一)中,有说道present出来的视图和push出来的视图的转屏设置有些区别的,在这里重新说明一下,实际上在本质上是没有什么区别的。
push出来的视图的转屏方式,不是看push出来的controller里的设置,而是由controller所在的navigationController所决定的,navigationController是继承自UIViewController的。本质上,不管push出来多少个视图控制器,显示还是在navigationController下的,所以push出来的视图控制器的转屏方式取决于他的navigationController。
navigationController下的子控制器的转屏方向设置不取决于自己,取决于当前所在的navigationController,因为navigationController是继承于UIViewController的,所以在navigationController里进行转屏设置也是有效的。
为了方便不同界面的转屏设置,我们可以在BaseViewController中添加一个属性orientation,系统的UIViewController有一个属性interfaceOrientation,但是是只读的,不能设置。
@property(nonatomic,readonly) UIInterfaceOrientation interfaceOrientation NS_DEPRECATED_IOS(2_0,8_0) __TVOS_PROHIBITED;
我们可以复写这个属性或者重新写一个UIInterfaceOrientation类型的属性。
@interface BaseViewController : UIViewController
@property (nonatomic) UIInterfaceOrientation interfaceOrientation;
@end
设置初始默认值,并设置默认不支持转屏,在需要支持转屏的界面单独设置,也可以添加一个是否支持转屏的BOOL类型的属性。
@implementation BaseViewController
- (instancetype)init {
if (self = [super init]) {
_interfaceOrientation = UIInterfaceOrientationPortrait;
}
return self;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
_interfaceOrientation = UIInterfaceOrientationPortrait;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (BOOL)shouldAutorotate {
return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return _interfaceOrientation;
}
@end
那么有个问题,有navigationController下的控制器,这样设置是无效的,因为navi下的控制器的转屏取决于当前的nav,而不是子控制器。如果在navigationController下有A和B两个controller,A不支持旋转,B需要支持旋转,这怎么处理?在进入A页面的时候设置navi的转屏方式为不可旋转,进入B页面的时候设置为可旋转,原则上这样可以解决这个需求。那么是不是也要给navi写一个类似的属性呢,给navi也写一个这样的属性来控制问题能解决,但是这样就很麻烦。
有没有什么更简单使用的方法呢?
给UIViewController扩展一个设置屏幕旋转方向的属性orientation和一个是否支持屏幕旋转的属性, 注意尽量不要和系统的属性重名,以免覆盖系统提供的功能。
@interface UIViewController (Rotation)
//是否支持重力感应 注意不要和系统的shouldAutorotate属性重名,以免覆盖系统提供的功能
@property (nonatomic) BOOL autoRotate;
//方向 注意尽量不要和系统的interfaceOrientation属性重名,以免覆盖系统提供的功能
@property (nonatomic) UIInterfaceOrientation orientation;
@end
#import "UIViewController+Rotation.h"
#import <objc/runtime.h>
static NSString const *kUIViewControllerRotationKey = @"UIViewControllerRotationKey";
static NSString const *kUIViewControllerAutoRotateKey = @"UIViewControllerAutoRotateKey";
//为了和系统的分类UIViewController (UIViewControllerRotation)冲突,分类命名为Rotation
@implementation UIViewController (Rotation)
- (void)setOrientation:(UIInterfaceOrientation)orientation {
NSNumber *number = [NSNumber numberWithInteger:orientation];
objc_setAssociatedObject(self, &kUIViewControllerRotationKey, number, OBJC_ASSOCIATION_RETAIN);//注意这里不能用OBJC_ASSOCIATION_ASSIGN
}
- (UIInterfaceOrientation)orientation {
NSNumber *number = objc_getAssociatedObject(self, &kUIViewControllerRotationKey);
if (number) {
return [number integerValue];
}
return UIInterfaceOrientationUnknown;
}
- (void)setAutoRotate:(BOOL)autoRotate {
NSNumber *number = [NSNumber numberWithBool:autoRotate];
objc_setAssociatedObject(self, &kUIViewControllerAutoRotateKey, number, OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)autoRotate {
NSNumber *number = objc_getAssociatedObject(self, &kUIViewControllerAutoRotateKey);
if (number) {
return [number boolValue];
}
return YES;
}
@end
这样就可以随心所欲的使用这两个属性进行对屏幕旋转的设置了。
- (BOOL)shouldAutorotate {
return self.autoRotate;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return self.orientation;
}
二、关于强制转屏的补充
在iOS开关中屏幕旋转(一)中国有提到过强转屏。
需要注意的是,只有在支持重力感应的情况下才能使用强制转屏方法,否则是转不了的。有些应用是不支持重力感应,但是支持某些界面用户手动去切换全屏。这种需求就需要我们在强制转屏前将autoRotate属性设置为YES,并同时需要实现支持重力感应的方法,手动切换全屏才会有效,或者使用其他的全屏方法。
- (void)rotateScreen:(UIInterfaceOrientation)orientation
{
//转屏时,先设置支持重力感应,否则此方法无效
self.navigationController.autoRotate = YES;
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)])
{
NSNumber *num = [[NSNumber alloc] initWithInt:orientation];
[[UIDevice currentDevice] performSelector:@selector(setOrientation:) withObject:(id)num];
[UIViewController attemptRotationToDeviceOrientation];
}
SEL selector=NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation =[NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = orientation;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
self.navigationController.autoRotate = NO;
self.navigationController.orientation = orientation;
}
-(BOOL)shouldAutorotate
{
return self.autoRotate;
}
如上设置,不支持重力感应,但是支持用户手动切换屏幕旋转,只支持手动全屏的需求时,这样处理。