github Demo 鏈接
0. 橫屏應用領域
- 游戲
- 視頻播放、直播
- 展示內容較多且有較多細節的,VR、全景、攝影教學、股市數據等App
1. UI適配
iOS6開始禁止使用setOrientation:私有方法,開發人員僅能通過project->Device Orientation設置對應方向,然而對于大量采用frame形式組織頁面布局的App來說,適配橫屏簡直就是災難。
那么原先僅支持豎屏的App如何快速適配橫屏呢?
- 根據需求,Device Orientation中新增設備支持方向
- 保持原先App結構,BasicViewController 默認支持Portrait,業務VC自行適配橫屏邏輯
- 測試LandscapeViewController,dismiss or pop or push 各種形式,對于其他頁面的影響,對StatusBar的影響
- 測試橫屏模式冷、熱啟動App是否導致頁面布局錯亂,比如,橫屏H5頁面喚起App,plus橫屏模式喚起App
如果全新的一個項目想要支持橫豎屏&Universal App 需要符合autolayout,也可以用Masonry。
2. 方向類型
1. 方向類型:
- Device Orientation
- Interface Orientation
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
注意InterfaceLandscape與DeviceLandscape取反
2. 方向類型獲?。?/h4>
3. 如何支持橫屏
1. 方法一:
transition, 通過選擇View的形式達到橫屏效果,判斷依據:鍵盤,上拉快捷方式方向不一致。適合簡單不需要鍵盤輸入交互,比如簡單的視頻播放。
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI_2);
CGFloat scale = 0.5;
transform = CGAffineTransformScale(transform, scale, scale);
self.xxView.transform = transform;
[UIView animateWithDuration:[[UIApplication sharedApplication] statusBarOrientationAnimationDuration] animations:^{
self.xxView.transform = CGAffineTransformIdentity;
self.xxView.frame = fullScreenVC.view.bounds;
}completion:^(BOOL finished) {
}];
2. 方法二:
通過私有API形式調用
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = orientation;
// 從2開始是因為0 1 兩個參數已經被selector和target占用
[invocation setArgument:&val atIndex:2];
[invocation invoke];
這種形式可行,但是存在被拒審的風險,被否決掉!
3. 方法三:
如果是 TabBarController & NavigationController結構的,為了支持頁面的橫屏需要做如下改造,工程設置支持對應的方向,默認ViewController只支持豎屏模式,只需要presentViewController即可,缺點在于無法適用之前push的導航容器,如有需要可以自行創建:
-
TabBarController:
- supportedInterfaceOrientations 需要向上拋到NavigationController的supportedInterfaceOrientations
- shouldAutorotate也是同理
-
NavigationController
- return self.topviewcontroller supportedInterfaceOrientations
- return self.topViewController shouldAutorotate
-
viewController
- 默認不用設置方向,前提基于BasicVC,默認豎屏
- 有需要再去設置supportInterfaceOrientation & shouldAutorotate
-
注意事項:
- shouldAutorotate 設置為NO后,當前VC只支持默認UIInterfaceOrientationMaskPortrait方向
4. 方法四:
- iOS8~iOS10+目前都有效:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSNumber *orientationUnknown = [NSNumber numberWithInt:UIInterfaceOrientationUnknown];
[[UIDevice currentDevice] setValue:orientationUnknown forKey:@"orientation"];
NSNumber *orientationTarget = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSNumber *orientationUnknown = [NSNumber numberWithInt:UIInterfaceOrientationUnknown];
[[UIDevice currentDevice] setValue:orientationUnknown forKey:@"orientation"];
NSNumber *orientationTarget = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscapeRight;
}
- (BOOL)shouldAutorotate
{
return YES;
}
在viewWillAppear && viewWillDisAppear時,強制通過KVC設置Orientation,這里并沒有涉及到私有API,不會被拒審,只能說這是一種不安全的方式來實現屏幕旋轉。
- 擴展:
- 強制改變當前頁面的Orientation:
- (void)landscapeAction:(UIButton *)button
{
if (self.supportedInterfaceOrientations == UIInterfaceOrientationMaskPortrait) {
[self blackMagicChangeOrientation:UIInterfaceOrientationLandscapeRight];
} else {
[self blackMagicChangeOrientation:UIInterfaceOrientationPortrait];
}
}
- (void)blackMagicChangeOrientation:(UIInterfaceOrientation)orientation
{
UIViewController *viewController = [[UIViewController alloc] init];
[viewController setModalPresentationStyle:UIModalPresentationCurrentContext];
viewController.view.frame = CGRectZero;
[self.navigationController pushViewController:viewController animated:NO];
NSNumber *orientationUnknown = [NSNumber numberWithInt:UIInterfaceOrientationUnknown];
[[UIDevice currentDevice] setValue:orientationUnknown forKey:@"orientation"];
NSNumber *orientationTarget = [NSNumber numberWithInt:orientation];
[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];
[self.navigationController popViewControllerAnimated:NO];
NSLog(@"current self.view. x: %f, y: %f, width: %f, height: %f", self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
}
看到一坨代碼是否想吐,的確,這種方式實際上通過push、pop一個viewController來觸發Orientation的改動已影響現有的ViewController,目前測試結果有效。
- 注意事項:
- iOS8及以上版本以下代碼失效:
UIViewController *vc = [[UIViewController alloc]init];
[self presentModalViewController:vc animated:NO];
[self dismissModalViewControllerAnimated:NO];
- viewWillDisappear、viewWillAppear等VC生命周期內self.view.frame變化,會影響頁面布局。
5. 坑爹的交互邏輯
- 橫屏push橫屏
采用上述方法四即可實現橫屏push橫屏效果。
LandscapeViewController *newLandscapeVC = [LandscapeViewController new];
[self.navigationController pushViewController:newLandscapeVC animated:YES];
詳情可參考Demo中的LandscapeViewController中pushVC實現。
6. 橫屏遇到的一些問題:
0. Plus 橫屏冷啟動App
為了避免以橫屏模式直接啟動App,需要在appdidFinishLaunch中先設置好:
[UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationPortrait;
這行代碼需要在放在window之前。
可以參考這個解決方案
1. iOS10 橫屏BUG
估計是系統的bug,在各大視頻軟件均遇到過這種情況,特別是在未開啟鎖方向的情況下。
2. 開啟App支持方向,且只支持一個角度,需要UIViewController支持方向收斂
為了解決為了解決iOS8及以上首次裝機橫屏進來的時候出現Alert的時候應用出現錯亂情況,收斂支方向,通過UIViewController的Category來實現默認的Orientation。因為iOS8以后系統默認的使用的Altert均為UIViewController,但并沒有設置Orientation,結果首次安裝用戶橫屏狀態下,頁面都錯亂了。
3. StatusBarFrame監聽通知
TabBarController接受了StatusBarFrame的通知,MainTab上的后面幾個ViewController沒有展示的情況下,先走到了viewDidLoad,這時View拿到的屏幕Frame都是橫屏比例,結果頁面就錯亂了,最終通過監聽StatusBarFrameChange中異常處理解決了提前走到viewDidLoad的情況。
7. 想法:
在一個運行數年之久的豎屏App開啟橫屏模式的確有很大挑戰,頁面布局,頁面跳轉原有的邏輯在新模式下都需要改造,也許還有更好的辦法解決橫屏問題,有待繼續研究。
橫屏push橫屏,非官方推薦交互邏輯,一般采用present形式且無push下個頁面的形式,這樣設計有點反人類。
參考YouTube、騰訊視頻,均采用頁面自刷新形式來更新數據源。這樣可以減少橫屏模式下push方式,減少采坑點。