最近项目中有个锁定横屏的需求。具体是通过url进入h5页面时,url中有参数控制横屏进入webview页面,并能锁定横屏显示。于是研究了下横竖屏切换控制,在进入横屏的要求上也遇到了些坑,最终得到比较好的解决,记录如下:
presentViewController
supportedInterfaceOrientations 和 preferredInterfaceOrientationForPresentation方法
如果当前viewcontroller是通过presentViewController
的方式切换出来的,我们可以通过preferredInterfaceOrientationForPresentation方法设置视图默认显示的方向。同时通过supportedInterfaceOrientations方法设置视图支持的显示方向。
//不支持自动旋转,iOS6以后支持的方法
- (BOOL)shouldAutorotate
{
return NO;
}
//支持横向
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscapeRight;
}
//当视图是通过 presentViewController 出来的时候,设定视图默认显示的方向为横向
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationLandscapeRight;
}
//IOS6以前的旋转控制方法,这里加上适配IOS5
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return toInterfaceOrientation == UIInterfaceOrientationLandscapeRight;
}
pushViewController
如果viewcontroller是通过push的方式
切换进来的(实际上我们项目中遇到的大多数场景都是这种),只是设置当前VC的横竖屏控制权限是不够的。因为push栈里各个视图控制器的横竖屏控制权限是由根VC决定的,所以在通常的TNV架构
(TabbarController->NavigationController->ViewController)里,横竖屏控制的属性我们要从TabbarController一路获取到当前ViewController。
@interface LSTabBarController : UITabbarController
@implementation LSTabBarController
- (BOOL)shouldAutorotate
{
return [self.selectedViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return [self.selectedViewController supportedInterfaceOrientations];
}
@end
@interface LSNavigationRotateController : UINavigationController
@implementation LSNavigationRotateController
- (BOOL)shouldAutorotate
{
return [self.topViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
@end
再在需要设置横屏的VC上设置好相应方法即可。
解决了吗?并没有!
看似大功告成,当我调试时,立刻发现一个问题:当我们从视图A(竖屏)push切换到视图B(横屏)的时候,视图不会自动发生横竖屏切换,而是当设备改变方向的时候才会根据代码中对应的方法设置去改变当前视图的横竖屏方向。
原因是shouldAutorotate
和supportedInterfaceOrientations
方法只有设备改变方向的时候才会被调用,而进入视图的时候是不会被触发调用的。更悲伤的是上面presentVC里的preferredInterfaceOrientationForPresentation
方法在pushVC里也不起作用了,事实上这个方法系统只在模态动画(modal)展示视图时才会调用。
既然进入视图时系统不能自动触发转屏,那我们就手动触发。这里currentDevice的orientation属性没有公开的设置方法,我们用KVC的方式修改。
- (void)viewWillAppear:(BOOL)animated{
[self forceToOrientation:UIInterfaceOrientationLandscapeLeft];
}
- (void)viewWillDisappear:(BOOL)animated{
[self forceToOrientation:UIInterfaceOrientationPortrait];
}
- (void)forceToOrientation:(UIDeviceOrientation)orientation{
NSNumber *orientationUnknown = [NSNumber numberWithInt:UIInterfaceOrientationUnknown];
[[UIDevice currentDevice] setValue:orientationUnknown forKey:@"orientation"];
NSNumber *orientationTarget = [NSNumber numberWithInt:orientation];
[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];
}
为什么每次设置orientation的时候都先设置为UnKnown?因为在视图B回到视图A时,如果当时设备方向已经是Portrait,再设置成Portrait会不起作用(直接return)。
好了吧?还是没有~
加上手动触发转屏后,我欣喜的看到页面在进入后自动转为横屏展示,可是当我返回时,又发现了一个蛋疼的问题:
我测试进入h5页面的场景是通过点击feed流页面的cardview里的网页链接,点击~push进h5页面~转横屏~一切都很好,但点返回后,发现原来链接所在的card里的图文排版乱了~~
像下面这样
我再三检查basicWebViewController里的-(void)viewWillDisappear方法,确实是调用了将屏幕转回Portrait的方法,上层页面的刷新布局的方法在返回时也正常调用,一切都感觉没啥问题。
折腾了好一阵,我发现一个现象,就是从h5页面返回后,只有点击链接所在的那个card里图文排版错乱,它上下的card都是正常显示的(那些card也都在屏幕里)。于是我找到处理card点击链接的代码,发现了这个:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.pressingActiveRange) {
id<WBTextActiveRange> activeRange = self.pressingActiveRange;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self eventDelegateDidPressActiveRange:activeRange];
});
_touchesBeginPoint = CGPointZero;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 若用户点击速度过快,hitRange高亮状态还未绘制又取消高亮会导致没有高亮效果
// 故延迟执行
[self setPressingActiveRange:nil];
[[self eventDelegateContextView] setNeedsDisplay];
});
}
}
哈!在调用delegate响应点击事件后,代码里还做了这么一件事:延迟执行取消高亮(链接点击有高亮效果)的重新绘制!可以猜想执行绘制的时候屏幕也处在旋转的过程中,这中间的状态很难确定,自然绘制出来的排版是乱的,而在h5页面返回时又没有相应的重新绘制的操作,导致了返回后这个响应链接的card排版错乱,这也解释了其他card能正常显示。
找到了原因,解决方法就很简单。在进入页面的viewWillAppear方法里,把旋转屏幕的操作改成延迟执行:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self forceToOrientation:UIInterfaceOrientationLandscapeLeft];
});
这样保证绘制工作结束后(下一个runloop)再执行转屏,从而避免了排版错乱的问题。
一些感想
因为选择锁定横屏这是浏览器做为一个通用的模块给其他业务提供的服务,在出现问题时,自然不能改动外面业务的逻辑,而只能从浏览器模块本身找解决办法。上面的方法比较好的解决了我遇到的问题,也可以说是应对潜在这一类问题(排版)的通用解决办法。