1、两个Orientation
1.1设备的物理方向(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
}
1.2界面的显示方向(UIInterfaceOrientation)
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
}
注意:
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
两者是相反的,经过测试发现:
在【General】 -->【Deployment Info】-->【Device Orientation】
勾选Landscape Right
设备支持的方向是听筒在左,Home键在右的方向,而
此时打印的device方向是UIDeviceOrientationLandscapeLeft
打印的Interface的方向是UIInterfaceOrientationLandscapeLeft
这个是ios6之后新增的一组枚举值,使用组合时更加方便,具体两者有什么区别,暂时还没搞清楚,使用时根据返回值类型选择正确的格式,避免可能出现的bug
typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
}
1.3获取Orientation
- 设备
[UIDevice currentDevice].orientation
@property(nonatomic,readonly) UIDeviceOrientation orientation; // return current device orientation. this will return UIDeviceOrientationUnknown unless device orientation notifications are being generated.
如果关闭了系统的横竖屏切换开关,即系统层级只允许竖屏时,再通过上述方式获取到的设备方向将是
UIDeviceOrientationPortrait
。
- 界面
self.interfaceOrientation ios之后8废除
[UIApplication sharedApplication].statusBarOrientation
2、默认方向
2.1 Device
打开【General】 -->【Deployment Info】-->【Device Orientation】
可以发现Xcode
默认支持的设备方向有Portrait
、Landscape Left
、Landscape Right
,Upside Down
方向是不支持的。
此处我们把Upside Down
勾中,发现在iPad中已经默认支持四个方向,但在iPhone中UpsideDown
方向不会发生变化。
原因:
**此处是设置支持的设备方向,界面看到的方向是Interface的方向,此时在全部选中的情况下(UIInterfaceOrientationMask)supportedInterfaceOrientations
方法,
iPad默认返回UIInterfaceorientationMaskAll
,
iPhone默认返回UIInterfaceOrientationMaskAllButUpsideDown
。
iPhone若要支持UpsideDown
,需重写(UIInterfaceOrientationMask)supportedInterfaceOrientations
**
具体设置方法,下面再详细介绍。
2.2 Interface
打开【Info】 -->【Custom iOS Target Properties】-->【Supported interface orientations】
默认和【General】 -->【Deployment Info】-->【Device Orientation】
值一样
3、全局设置
Device的方向是我们是通过改变握持方向去改变的,不是代码能够改变的,所以这里对
Device Orientation
只有Support
的概念。通过【General】 -->【Deployment Info】-->【Device Orientation】
设置。
设置Interface Orientation有三种途径
3.1 Info.plist设置
如图所示,可以通过修改Info.plist
里的UISupportedInterfaceOrientations
的属性修改Interface
的SupportOrientation
。
设置Interface
的界面的Orientation
,plist
里并没有直接给出,需要我们自行添加,添加方式和UISupportedInterfaceOrientations
相同。
注意:
在【General】 -->【Deployment Info】-->【Device Orientation】
设置设备支持方向时,plist
里的Supported interface orientations
会根据设备方向自动添加Interface
的支持方向。如不需要某方向的UISupportedInterfaceOrientations
,可以在Info.plist
里手动修改,该修改并不会影响Device
的SupportOrientation
。
3.2 UIWindow设置
iOS6的UIApplicationDelegate
提供了下述方法,能够指定UIWindow
中的界面的屏幕方向:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window NS_AVAILABLE_IOS(6_0);
该方法默认值为Info.plist
中配置的Supported interface orientations
项的值。
3.3 UIViewcontroller设置
三个方法
//Interface的方向是否会跟随设备方向自动旋转,如果返回NO,后两个方法不会再调用
- (BOOL)shouldAutorotate {
return YES;
}
//返回直接支持的方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
//返回最优先显示的屏幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
解释
1.第二个方法,在iPad上的默认返回值是UIInterfaceOrientationMaskAll
,iPhone上的默认返回值是UIInterfaceOrientationMaskAllButUpsideDown
;
2.在前面DeviceOrientation
即使全部勾选了,若要iPhone支持UpsideDown
,也要在viewcontroller
里重写第二个方法。返回包含UpsideDown
的方向;
3.第三个方法,比如同时支持Portrait
和Landscape
方向,但想优先显示Landscape
方向,那软件启动的时候就会先显示Landscape
,在手机切换旋转方向的时候仍然可以在Portrait
和Landscape
之间切换;
这三个方法是iOS6之后才有的,有个特点是只在两种情况下才生效
1.当前
viewController
是window
的rootViewController
。
2.当前viewController
是modal
模式的,即此viewController
是调用presentModalViewController
而显示出来的.
在以上两种情况中,supportedInterfaceOrientations
的方法会作用于当前的viewController
和所有的childViewController
,如果不是以上的两种情况下的viewController
,UIKit
不会执行上述方法。
**这三种方法控制规则的交集就是一个
viewController
的最终支持的方向;
如果最终的交集为空,在iOS6以后会抛出UIApplicationInvalidInterfaceOrientationException
崩溃异常。**
4、自定义设置
在实际的项目需求过程中,往往是有一些需要固定方向,有些需要支持旋转适应的。刚才上面提到过,通过UIViewController
的三个方法设置Orientation
时,只有在是window
的rootViewController
或者modal
模式下才生效。单独设置某个viewController
并没有效果。这样就给我们带来了一些麻烦,通过调查研究,大概有这么几种解决方案。
4.1 在rootViewController里加判断
如:
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
if([[self topViewController] isKindOfClass:[subViewController class]])
return UIInterfaceOrientationMaskAllButUpsideDown;
else
return UIInterfaceOrientationMaskPortrait;
}
或者在UINavigationController
或UITabBarController
里重写
-(UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return self.selectedViewController.supportedInterfaceOrientations;
}
-(BOOL)shouldAutorotate
{
return [self.selectedViewController shouldAutorotate];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
UINavigationController
使用self.topViewController
UITabBarController
使用self.selectedViewController
然后在viewControlle
r重写这三个方法,这样就巧妙的绕开了UIKit
只调用rootViewController
的方法的规则. 把决定权交给了当前正在显示的viewController
.
但是
这样是可以在当前viewController
达到预期效果,但是在返回上一页时,或者在当前页面不不支持的方向的上一页进来时,不能立即达到预期状态,需要设备方向更换一次才能恢复正常。
解决方案:
#pragma mark -UITabBarControllerDelegate
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
[self presentViewController:[UIViewController new] animated:NO completion:^{
[self dismissViewControllerAnimated:NO completion:nil];
}];
}
#pragma mark -UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[self presentViewController:[UIViewController new] animated:NO completion:^{
[self dismissViewControllerAnimated:NO completion:nil];
}];
}
这样就会触发-(BOOL)shouldAutorotate
方法和
-(UIInterfaceOrientationMask)supportedInterfaceOrientations
方法,但是会闪一下,依然不完美。
4.2 把需要单独设置的viewController添加在一个独立的navgationController里
这种方法不适用rootViewController
是UITabBarController
的, 不推荐使用。
4.3 UIView.transform设置
代码如下:
//设置statusBar
[[UIApplication sharedApplication] setStatusBarOrientation:orientation];
//计算旋转角度
float arch;
if (orientation == UIInterfaceOrientationLandscapeLeft)
arch = -M_PI_2;
else if (orientation == UIInterfaceOrientationLandscapeRight)
arch = M_PI_2;
else
arch = 0;
//对navigationController.view 进行强制旋转
self.navigationController.view.transform = CGAffineTransformMakeRotation(arch);
self.navigationController.view.bounds = UIInterfaceOrientationIsLandscape(orientation) ? CGRectMake(0, 0, SCREEN_HEIGHT, SCREEN_WIDTH) : initialBounds;
statusBar
不会自己旋转,这里要首先设置statusBar
的方向。ios9后setStatusBarOrientation
方法废除,在ios10中测试,不设置StatusBarOrientation
会自动调整- 我们这里选择的是
self.navigationController
进行旋转,当然也可以是self.view
或者self.window
,都可以,最好是全屏的view
.- 我们需要显示的设置
bounds
,UIKit
并不知道你偷偷摸摸干了这些事情。-(BOOL)shouldAutorotate
方法,应返回NO
在iOS 9 之后横屏时,状态栏会消失。
解决方法:确保
Info.plist
中的【View controller-based status bar appearance】
为YES
,然后重写viewController
的- (BOOL)prefersStatusBarHidden
,返回值是NO
。详细参考iOS-UIStatusBar详细总结
- (BOOL)prefersStatusBarHidden
{
return NO;
}
完美的方法待继续研究!!!
5、强制旋转
5.1 setOrientation的方法
setOrientation
在iOS3以后变为私有方法了,不能直接去调用此方法,否则后果就是被打回。
不能直接调用,但是可以间接的去调用,下面的方法就是利用 KVO机制去间接调用,多次验证不会被打回,放心!
-(void)viewWillAppear:(BOOL)animated{
//首先设置UIInterfaceOrientationUnknown欺骗系统,避免可能出现直接设置无效的情况
NSNumber *orientationUnknown = [NSNumber numberWithInt:UIInterfaceOrientationUnknown];
[[UIDevice currentDevice] setValue:orientationUnknown forKey:@"orientation"];
NSNumber *orientationTarget = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];
}
5.2 UIview.transform的方法
方法见4.3
参考文章:
iOS屏幕旋转学习笔记
[iOS]Orientation 想怎么转就怎么转
iOS 知识小集(横竖屏切换)
iOS强制改变物理设备方向的进阶方法