想怎么转就怎么转

最近在做直播,用的是腾讯云,但是主播界面需要横屏显示,而且只能横屏,整个项目也只有这个界面横屏。
发现腾讯云在这一块提供了对流的控制,但是并没有提供对界面的控制,需要自己去实现界面的控制,主要是界面各种交互,各种效果,各种逻辑,所以必须自己控制选择,期间也遇到了不少坑。
所以就整理了一下,当然这里同样适合iPad适配,相信以后在屏幕旋转和旋转相关适配这一块应该是没有问题了!

相关枚举:首先需要知道苹果提供的关于屏幕的一些枚举值

UIDeviceOrientation:

1
2
3
4
5
6
7
8
9
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
};

UIInterfaceOrientation:

1
2
3
4
5
6
7
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
};

从宏定义可知,device方向比interface多了两个定义:
UIDeviceOrientationFaceUp和UIDeviceOrientationFaceDown,分别表示手机水平放置,屏幕向上和屏幕向下。

两种orientation:然后就是两种相关的orientation

device orientation

设备的物理方向,由类型UIDeviceOrientation表示,当前设备方向获取方式:

1
2
// return current device orientation.  this will return UIDeviceOrientationUnknown unless device orientation notifications are being generated.
[UIDevice currentDevice].orientation //该属性的值一般是与当前设备方向保持一致的。

注意点1:推荐方式

1
2
3
4
5
6
if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) {
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
}
NSLog(@"%d",[UIDevice currentDevice].orientation);

[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];

注意点2:横竖屏关闭

1
2
如果关闭了系统的横竖屏切换开关,即系统层级只允许竖屏时,再通过上述方式获取到的设备方向将永远是UIDeviceOrientationUnknown。
可以通过Core Motion中的CMMotionManager来获取当前设备方向。

interface orientation

界面显示的方向,由类型UIInterfaceOrientation表示。当前界面显示方向有以下两种方式获取:

设备的物理方向,由类型UIDeviceOrientation表示,当前设备方向获取方式:

1
2
NSLog(@"%d",[UIApplication sharedApplication].statusBarOrientation);
NSLog(@"%d",viewController.interfaceOrientation);

即可以通过系统statusBar的方向或者viewController的方向来获取当前界面方向。

区别

通过UIDevice获取到的设备方向在手机旋转时是实时的。
通过UIApplication的statusBar或者viewController获取到的界面方向在下述方法:调用以后才会被更改成最新的值。

1
2
NSLog(@"%d",[UIApplication sharedApplication].statusBarOrientation);
NSLog(@"%d",viewController.interfaceOrientation);

相关方法

1
2
3
4
5
6
7
8
9
10
11
12
// Applications should use supportedInterfaceOrientations and/or shouldAutorotate..
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS(2_0, 6_0);

// New Autorotation support.
- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0);
- (NSUInteger)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0);
// Returns interface orientation masks.
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0);

// call this method when your return value from shouldAutorotateToInterfaceOrientation: changes
// if the current interface orientation does not match the current device orientation, a rotation may occur provided all relevant view controllers now return YES from shouldAutorotateToInterfaceOrientation:
+ (void)attemptRotationToDeviceOrientation NS_AVAILABLE_IOS(5_0);

如何旋转

全局控制

Info.plist文件中,有一个Supported interface orientations,可以配置整个应用的屏幕方向,此处为全局控制。

UIWindow

iOS6的UIApplicationDelegate提供了下述方法,能够指定 UIWindow 中的界面的屏幕方向:

1
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window  NS_AVAILABLE_IOS(6_0);

该方法默认值为Info.plist中配置的Supported interface orientations项的值。

iOS中通常只有一个window,所以此处的控制也可以视为全局控制。

controller

只有以下两种情况:

当前controller是window的rootViewController
当前controller是modal模式的时候,orientations相关方法才会起作用(才会被调用),当前controller及其所有的childViewController都在此作用范围内。

最终支持的屏幕方向

前面所述的3种控制规则的交集就是一个controller的最终支持的方向;

如果最终的交集为空,在iOS6以后会抛出UIApplicationInvalidInterfaceOrientationException崩溃异常。

总结来说改变Orientation有三种途径

当手机的重力感应打开的时候, 如果用户旋转手机, 系统会抛发UIDeviceOrientationDidChangeNotification 事件.

您可以分别设置Application和UIViewcontroller支持的旋转方向.Application的设置会影响整个App, UIViewcontroller的设置仅仅会影响一个viewController(IOS5和IOS6有所不同,下面会详细解释).

当UIKit收到UIDeviceOrientationDidChangeNotification事件的时候, 会根据Application和UIViewcontroller的设置, 如果双方都支持此方向, 则会自动屏幕旋转到这个方向. 更code的表达就是, 会对两个设置求与,得到可以支持的方向. 如果求与之后,没有任何可支持的方向, 则会抛发UIApplicationInvalidInterfaceOrientationException异常.

当然,你还可以对View进行旋转,但是这样会有一些需求上的问题,比如状态栏,键盘等就没有办法,只能自己通过其他方式控制!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;

当然我们可以对当前viewController进行旋转, 对任何view旋转都可以.但是, 你会发现navigationBar还横在那里. 所以, 我们最好对一个占满全屏的view进行旋转. 在这里我们旋转的对象是self.navigationController.view, 当然self.window也可以, help yourself~
我们需要显式的设置bounds. UIKit并不知道你偷偷摸摸干了这些事情, 所以没法帮你自动设置.

具体实现

借助通知来控制界面的横竖屏切换。

还是整个App中大部分界面都是竖屏,某个界面可以横竖屏切换的情况。
首选这只plist只支持竖屏:Portrait(Home朝下)

然后在特殊的视图控制器里的ViewDidLoad中注册通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil];

通知方法的实现过程:

- (void)deviceOrientationDidChange
{
NSLog(@"deviceOrientationDidChange:%ld",(long)[UIDevice currentDevice].orientation);
if([UIDevice currentDevice].orientation == UIDeviceOrientationPortrait) {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
[self orientationChange:NO];
//注意: UIDeviceOrientationLandscapeLeft 与 UIInterfaceOrientationLandscapeRight
} else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft) {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[self orientationChange:YES];
}
}

- (void)orientationChange:(BOOL)landscapeRight
{
if (landscapeRight) {
[UIView animateWithDuration:0.2f animations:^{
self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
self.view.bounds = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}];
} else {
[UIView animateWithDuration:0.2f animations:^{
self.view.transform = CGAffineTransformMakeRotation(0);
self.view.bounds = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}];
}
}

// 用到的两个宏:
#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
#define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)

最重要的一点:

需要重写如下方法,并且返回NO。这样,在设备出于横屏时,界面就会变成横屏,设备处于竖屏时,界面就会变成竖屏。

1
2
3
4
- (BOOL)shouldAutorotate
{
return NO;
}

但是这样会遇到两个坑

上面方式二,因为【General】–>【Device Orientation】因为只设置了竖屏,所以当横屏时,如果有键盘弹出,键盘是竖屏时的样式。
解决办法:在【General】–>【Device Orientation】中加上横屏时的方向。

如果VieController 是放在UINavigationController或者UITabBarController中,需要重写它们的方向控制方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// UINavigationController:
- (BOOL)shouldAutorotate
{
return [self.topViewController shouldAutorotate];
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}

// UITabBarController:
- (BOOL)shouldAutorotate
{
return [self.selectedViewController shouldAutorotate];
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return [self.selectedViewController supportedInterfaceOrientations];
}

如果想要点击某个按钮之后,强制将竖屏显示的界面变成横屏呢?
有人可能会想到这样写:

1
2
3
4
5
// 横屏
- (IBAction)landscapAction:(id)sender {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[self orientationChange:YES];
}

但是按照上面的写法,会导致返回到之前的界面时,视图方向错误,即使返回前执行如下代码:

1
2
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
[self orientationChange:NO];

结果发现也没有作用,下面是在开源工程中无意看到的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 横屏
- (IBAction)landscapAction:(id)sender {
[self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
}

// 竖屏
- (IBAction)portraitAction:(id)sender {
[self interfaceOrientation:UIInterfaceOrientationPortrait];
}

- (void)interfaceOrientation:(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];
}
}

通过属性控制

项目需求(场景): 整个项目不需要旋转(当然了,所有方向的屏幕适配都做好的情况下是没问题的,但是没有必要这么劳民伤财。)的前提下,播放界面控制器需要支持屏幕的其他方向的旋转。

先设置:targets——>general——>device Orientation——>支持的旋转方向

在 AppDelegate.m 里面

1
2
3
4
5
6
7
-(UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
if (self.allowRotation) {//当允许时,支持所有方向
return UIInterfaceOrientationMaskAll;
}
//否则 就只有竖屏
return UIInterfaceOrientationMaskPortrait;
}

在你想要支持 旋转的控制器 需要导入#import “AppDelegate.h”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];

self.navigationController.navigationBarHidden = YES;

//改变AppDelegate的appdelegete.allowRotation属性
AppDelegate *appdelegete = (AppDelegate *)[UIApplication sharedApplication].delegate;
appdelegete.allowRotation = YES;
}



- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
self.navigationController.navigationBarHidden = NO;

AppDelegate *appdelegete = (AppDelegate *)[UIApplication sharedApplication].delegate;
appdelegete.allowRotation = NO;
}

当你写完这些的时候发现好像可以了,还有问题。

但你横屏播放的状态下 直接返回上个控制器(pop)的时候.
发现那个控制器也是横屏的(当你竖屏时,它会转过来,但是再旋转就不行了。问题就是pop回来不能使横屏啊,应该直接竖屏才是啊)
比如pop回的那个控制器叫 TextViewController
在TextViewController.m里

1
2
3
4
//屏幕方向操作
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

此方法是UIKit框架中UIViewController.h的公布方法,iOS 6.0后加入

总结关于屏幕旋转这一块并不难,苹果提供了很多现成的方式,或者说只需要调用方法,当然还是根据具体需求进行调整。

适当的控制对应的控件,调用相应的方法,那么不管是iPhone还是iPad你想怎么转就怎么转!

坚持原创技术分享,您的支持将鼓励我继续创作!