关于横竖屏适配 也没做过,今天读别人的源码,遇到了。为了了解清楚,就系统的学习一下。
一 横竖屏方向枚举
关于横竖屏一共有三种枚举 UIInterfaceOrientation UIInterfaceOrientationMask UIDeviceOrientation。
1.1 UIInterfaceOrientation与UIDeviceOrientation
为什么这两个放在一起说,好吧,你看看下面这个枚举定义:
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
对于iOS
设备来讲,屏幕状态由以上五种状态。上下翻转还是很好区分的,左右旋转可能就不是很好区分。
如果我们仔细看枚举值的话 会发现一些问题
在处于竖屏和上下翻转的状态下这两个枚举值是一样的,而处于横屏时这两个枚举值刚好相反
所以在有时你发现跟你预期的翻转方向不一样的时候,可能你用错了枚举。
UIDeviceOrientation 是设备的当前所处的方向,而且事实上他有六个值
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
UIDeviceOrientationUnknown,
UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom
UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top
UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right
UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left
UIDeviceOrientationFaceUp, // Device oriented flat, face up
UIDeviceOrientationFaceDown // Device oriented flat, face down
} __TVOS_PROHIBITED;
分别对应iPhone未知方向,竖直,上下反转,向左旋转,向右旋转,屏幕朝上,屏幕朝下 那么如何区分左右呢
UIDeviceOrientationLandscapeLeft,home键在右侧
UIDeviceOrientationLandscapeRight,home键在左侧
所以,UIDevice
顾名思义,事实上是用来判断设备方向的。
UIInterfaceOrientation 即当前页面的方向。
在设备进行横屏旋转的时候,为了横屏时上下不翻转,所以当Device处于Left时,界面应该是Right旋转。这样才不会使横屏时内容上下翻转。
所以对于横竖屏适配,使用的枚举大家一定要看好,使用UIInterfaceOrientation
。不要搞反。
1.2 UIInterfaceOrientationMask
这是iOS6之后新增的一组枚举值
typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),// home 键 在右
UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),//home键 在左
UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
} __TVOS_PROHIBITED;
事实上我们在横竖屏适配时,最常用的是这个枚举。这个枚举详细的列举了各种你需要的情况
二 开启横竖屏的权限
可以看到这种勾选方式允许你进行四个方向的配置,并且这种勾选方式会直接在你的项目plist文件中添加
但是由于在这里配置是对项目启动时lanuch界面产生影响,而往往我们又没有对lanuch进行横竖屏适配,所以在这个时候我们就需要使用第二种方式进行配置。
在项目中的AppDelegate文件中进行配置。
#pragma mark - InterfaceOrientation //应用支持的方向
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
搭配UIInterfaceOrientationMask
使用,你可以很方便的让你项目开启你所需要的横竖屏权限和限制条件。
三 在VC中如何控制横竖屏
我们都知道MVC架构,那么显而易见,在我们开启了项目的横竖屏的限制之后,需要在ViewController
进行相应的配置,才能真正实现横竖屏。
开启横竖屏,我们需要在VC中添加如下代码
// 设备支持方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAll;
}
// 默认方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait; // 或者其他值 balabala~
}
而对于横竖屏,手机端一般有两种情况,一种是手机没有开启横竖屏锁定,用户将手机横屏时触发的。对于第一种情况,我们只需要在VC中添加:
// 开启自动转屏
- (BOOL)shouldAutorotate {
return YES;
}
另一种是我们在项目中的某些条件下强行让屏幕横屏,例如大图预览,视频播放,等等。而对于这种情况,我们可以使用下面?这两种方法,都可以实现效果:
- (void)setInterfaceOrientation:(UIInterfaceOrientation)orientation {
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
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];
}
}
- (void)setInterfaceOrientation:(UIDeviceOrientation)orientation {
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:orientation] forKey:@"orientation"];
}
}
PS:这两个方法只有在- (BOOL)shouldAutorotate( return YES; )
时,才会生效。并且请注意使用的枚举值的不同。
四.横竖屏控制优先级
在我们接手一个项目后,说要添加一个某个界面横竖屏需求时,发现按照上面的方式配置了一圈,发现还是转!不!成!功!What F***!!!
事实上在这里我们要了解一个问题,就是关于横竖屏控制的优先级。对于限于VC
范围来讲优先级最高的是当前的window
的rootViewController
,而往往我们的项目结构是容器视图控制器控制VC
,tabBarController
控制navigationController
之后是VC
,而横竖屏控制的优先级也是跟你的项目架构一样。而且是一旦优先级高的关闭了横竖屏配置,优先级低的无论如何配置都无法做到横竖屏。所以在你接受这个需求的时候,你需要看一下根视图的配置。
对于这种情况,我们有两种处理方式,一种是通过模态的方式跳转的下个VC
,这个VC
是隔离出来的,不在你之前的容器里,不会受到rootViewController
的影响。
而另一种我们需要改造一下根视图的配置:
-(BOOL)shouldAutorotate {
return [[self.viewControllers lastObject] shouldAutorotate];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}
或者
-(BOOL)shouldAutorotate {
if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
return YES;
}
return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
return UIInterfaceOrientationMaskLandscapeLeft;
}
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
return UIInterfaceOrientationLandscapeLeft;
}
return UIInterfaceOrientationPortrait;
}
可以看到我们通过获取push栈中的最后一个VC
的属性,或指定特殊的VC
来进行rootViewController
的横竖屏设置。
当然也可以通过NSNotificationCenter
或者NSUserDefaults
的方式对这里的值进行设置,在这里我就不过多赘述了。
总之要知道优先级的问题,general
== appDelegate
>> rootViewController
>> nomalViewController
明白了权限的优先级以及开启的方法我想转屏就很显而易见了。
五.横竖屏适配
事实上旋转屏幕成功,对于iOS横竖屏问题我们只是完成了一半。另一半就是UI适配问题,其实这个要说起来就比较麻烦了,有些时候有很多case需要针对对应的业务条件来定制。但是无外乎几种实现思路。这里博主给大家抛几块砖哈:
首先我们要知道,当发生转屏事件时,系统的回调方法是:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
if (size.width > size.height) { // 横屏
// 横屏布局 balabala
} else {
// 竖屏布局 balabala
}
}
首推的方式是使用约束布局,在使用约束布局时,横竖屏转换时,在通常情况下约束条件会很相似,所以在布局上会极大的减少代码量。其次如果有个别的特殊问题,可以在上面的回调方法里面进行微调。
其次,对于转屏后,[UIScreen mainScreen].bounds.size
以及self.view.frame.size
的宽高系统会自动调换。即在横屏的时候width > height
。所以在我们进行常规布局的时候我们可以选择控件的frame
属性都与这两个属性进行比例换算。这样在当横竖屏转换的时候,重布局时,也会适应成对应屏幕下的布局。同样有需要特殊处理的布局,在上面的回调方法中进行细节微调即可。
对于子视图,在横竖屏切换时,会触发子视图重布局的方法:
- (void)layoutSubviews {
[super layoutSubviews];
// 通过状态栏电池图标来判断屏幕方向
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationMaskPortrait) {
// 竖屏 balabala
} else {
// 横屏 balabala
}
}
当然我只是说了几种比较简单的处理方式,和应对方法,对于整个项目都需要横竖屏适配的,我想还是一个比较复杂的过程。在实在处理不了的问题上,也可以通过写两套布局的方式来处理。至于过场动画,理论上如果你用约束和我说的比例布局的方式来写,基本系统会自动帮你这个问题给处理掉。但如果两种布局差距很大,你用了两套完全不同的布局,那这个你可能就要伤脑筋了。哈哈哈。不过有一些情况处理要求不严格的话可以使用截图过场大法来解决。不过这个就不是本文的设计范围了。大家如果感兴趣可以自己google一下。当然我日后的文章也可能会写到这了。到时候再来这里修改。
下面推荐几个符合的博客
//iPad横竖屏下的代码适配
https://www.jianshu.com/p/7cfabd741455
iOS 屏幕适配,autoResizing autoLayout和sizeClass图文详解