问题回放
如果我们在一个iOS项目中使用到了视频播放器就难免会遇到强制横竖屏的问题.这个老生常谈的问题我们只需要监听设备的方向改变的通知即可,一般情况是不会出现什么的问题的.但是事实真的是这样吗?网上在这方面的资料也是杂乱不堪,大部分只是说说如何进行强制横竖屏,没有进一步说明动画操作,虽然大部分的情况是没有任何问题的,可是当用户快速的从左横屏→竖屏→右横屏的时候就会出现PlayView的尺寸显示不正确的问题了.原因是当左横屏→竖屏的动画还未完成时(动画过程需要0.3s),设备已经完成了从竖屏到右竖屏的过程了.导致两个动画同时执行,界面就出现问题.当然了,还有如下的问题也是类似的.都是尺寸缩放动画错乱造成的!
错误代码如下
#define KmainWidth [UIScreen mainScreen].bounds.size.width
#define KmainHeight [UIScreen mainScreen].bounds.size.height
//添加监听
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(rotateScreenViews:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
#pragma mark - 屏幕旋转的主要动画操作方法
- (void)rotateScreenViews:(NSObject *)sender{
UIDevice* device = [sender valueForKey:@"object"];
if (device.orientation == UIDeviceOrientationPortrait ) {
self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainWidth/16.0*9);
}
if (device.orientation == UIDeviceOrientationLandscapeLeft ) {
self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);
}
if (device.orientation == UIDeviceOrientationLandscapeRight ) {
self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);
}
}
* 正常情况(此时宽高比:16:9)
* 动画执行错乱情况(此时宽高比:横屏情况下的宽高比)
解决思路
上面说到的这种问题我们该怎么解决呢?其实很简单,我们来自定义动画.通过布尔值(isPortraitFinished)来监控屏幕旋转的动画是否完成,如果完成我们才执行接下来的动画操作,或者结束操作.而且这里我加入了另外的一个布尔值(isFullScreen)来减少执行动画的次数.具体代码如下所示.
在.m的类扩展中有以下属性.
#define KmainWidth [UIScreen mainScreen].bounds.size.width
#define KmainHeight [UIScreen mainScreen].bounds.size.height
@interface FullScreenViewController ()
@property(nonatomic,strong)UILabel *playView;
@property(nonatomic,assign)BOOL isFullScreen;//是否是全屏,减少动画执行次数
@property(nonatomic,assign)BOOL isPortraitFinished;//是否已经完成了竖屏.
@end
viewDidLoad主要是来进行视频View的初始化,注册旋转通知等,如下所示.
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.playView];
self.isPortraitFinished = YES;//这个要开始设置为YES,不管在横屏还是竖屏的情况下都可以适应.
//注册横竖屏的通知
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(rotateScreenViews:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (UILabel *)playView{
if (_playView == nil) {
_playView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, KmainWidth, KmainWidth/16.0*9)];
_playView.backgroundColor = [UIColor lightGrayColor];
_playView.text = @"播放内容";
_playView.font = [UIFont systemFontOfSize:18];
_playView.textAlignment = NSTextAlignmentCenter;
}
return _playView;
}
在viewWillAppear中先设定屏幕的设备方向为竖屏方向,如下所示.
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
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 = UIInterfaceOrientationPortrait;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
现在正题来了,我们的旋转方法相比于问题回放中代码有何改变和优化呢? 这里首先进行第一步通知回调方法的实现,代码如下所示.
- (void)rotateScreenViews:(NSObject *)sender{
UIDevice* device = [sender valueForKey:@"object"];
if (device.orientation == UIDeviceOrientationPortrait && _isFullScreen) {
CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间
_isFullScreen = NO;
[UIView animateWithDuration:duration animations:^{
CGFloat nowWidth = 0;
if (KmainWidth > KmainHeight) {
nowWidth = KmainHeight;
}else{
nowWidth = KmainWidth;
}
NSLog(@"竖进行");
self.playView.frame = CGRectMake(0, 0, nowWidth, nowWidth/16.0*9);
self.isPortraitFinished = NO;
} completion:^(BOOL finished) {
NSLog(@"竖完成");
self.isPortraitFinished = YES;
[self portraitFinishaNextAction];
}];
}
if (device.orientation == UIDeviceOrientationLandscapeLeft && !_isFullScreen) {
CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间
_isFullScreen = YES;
[UIView animateWithDuration:duration animations:^{
if (self.isPortraitFinished != NO) {
self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);
NSLog(@"左右进行");
}
}completion:^(BOOL finished) {
NSLog(@"左右完成");
}];
}
if (device.orientation == UIDeviceOrientationLandscapeRight && !_isFullScreen) {
CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间
_isFullScreen = YES;
[UIView animateWithDuration:duration animations:^{
if (self.isPortraitFinished != NO) {
self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);
NSLog(@"左右进行");
}
} completion:^(BOOL finished) {
}];
}
}
在上一方法中,我们通过 isPortraitFinished 属性来隔断动画的执行.然后用户快速的旋转屏幕的话,我们会在第二个方法( portraitFinishaNextAction )中处理这种情况,当竖屏动画完成之后,我们还要判断当前设备的方向是否是竖屏,如果是,那么没有动画执行,如果不是的话,那么我们就再次执行旋转动画.代码如下所示.
//竖屏完成之后判断现在的手机设备的方向,如果是横竖屏,那么需要再次进行动画操作.
-(void)portraitFinishaNextAction{
UIDeviceOrientation orient = [UIDevice currentDevice].orientation;
if (orient == UIDeviceOrientationLandscapeLeft || orient == UIDeviceOrientationLandscapeRight) {
CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间
[UIView animateWithDuration:duration delay:duration options:UIViewAnimationOptionLayoutSubviews animations:^{
if (self.isPortraitFinished != NO) {
self.playView.frame = self.playView.frame;
}
} completion:^(BOOL finished) {
self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);
}];
}
}
好了,这里就完成我们的屏幕旋转动画的优化处理了,Demo链接地址会在文章尾部给出.
屏幕旋转的补充说明
这里有几点需要补充的,假定当期的播放页面可以在横屏的情况下进行返回上一个页面的操作.这时候我们需要先旋转屏幕,我们该如何实现呢?我们只需要在viewWillDisappear中写入如下代码即可.
//假设有上一级的页面 我们通过下面代码来进行竖屏切换,如果没有 去掉就好
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
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 = UIInterfaceOrientationPortrait;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
第二补充点就是我们如果做锁屏操作怎么做才合适呢?比如我现在的项目中就同时有直播和点播,难道我要写两套代码吗?不,我们只需要在AppDelegate中设定设备是否可以旋转,以及设备旋转的方向,这里的值我们可以随意设定代码如下所示.
AppDelegate.h中的属性代码
@property (nonatomic,assign)NSInteger allowRotate;
AppDelegate.m中的实现代码
#pragma mark - 设备是否可以旋转
//此方法会在设备横竖屏变化的时候调用
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
if (_allowRotate == 111) {
return UIInterfaceOrientationMaskLandscape;
}
if (_allowRotate == 999) {
return UIInterfaceOrientationMaskLandscapeRight;
}
if (_allowRotate == 666) {
return UIInterfaceOrientationMaskLandscapeLeft;
}
if (_allowRotate == 333) {
return UIInterfaceOrientationMaskPortrait;
}
if (_allowRotate == 1) {
return UIInterfaceOrientationMaskLandscape | UIInterfaceOrientationMaskPortrait;
}else{
return (UIInterfaceOrientationMaskPortrait);
}
}
// 返回是否支持设备自动旋转
- (BOOL)shouldAutorotate
{
if (_allowRotate == 999 || _allowRotate == 666) {
return NO;
}
if (_allowRotate == 1) {
return YES;
}
return NO;
}
这样我们只需要操作AppDelegate中的allowRotate属性即可.
结语
好了,这一次分享就到这里了.如果喜欢的话,欢迎点赞收藏,当然了,如果有任何问题都可以联系骚栋.或者在评论区提出来,我会及时回复的,谢谢.最后还是老惯例,附上实现代码的传送门.