iOS之橫屏那些事

github Demo 鏈接

0. 橫屏應用領域

  1. 游戲
  2. 視頻播放、直播
  3. 展示內容較多且有較多細節的,VR、全景、攝影教學、股市數據等App

1. UI適配

iOS6開始禁止使用setOrientation:私有方法,開發人員僅能通過project->Device Orientation設置對應方向,然而對于大量采用frame形式組織頁面布局的App來說,適配橫屏簡直就是災難。
那么原先僅支持豎屏的App如何快速適配橫屏呢?

  1. 根據需求,Device Orientation中新增設備支持方向
  2. 保持原先App結構,BasicViewController 默認支持Portrait,業務VC自行適配橫屏邏輯
  3. 測試LandscapeViewController,dismiss or pop or push 各種形式,對于其他頁面的影響,對StatusBar的影響
  4. 測試橫屏模式冷、熱啟動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>

Orientation獲取方式、區別

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:

    1. supportedInterfaceOrientations 需要向上拋到NavigationController的supportedInterfaceOrientations
    2. shouldAutorotate也是同理
  • NavigationController

    1. return self.topviewcontroller supportedInterfaceOrientations
    2. return self.topViewController shouldAutorotate
  • viewController

    1. 默認不用設置方向,前提基于BasicVC,默認豎屏
    2. 有需要再去設置supportInterfaceOrientation & shouldAutorotate
  • 注意事項:

    1. 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,不會被拒審,只能說這是一種不安全的方式來實現屏幕旋轉。

  • 擴展:
    1. 強制改變當前頁面的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. 想法:

  1. 在一個運行數年之久的豎屏App開啟橫屏模式的確有很大挑戰,頁面布局,頁面跳轉原有的邏輯在新模式下都需要改造,也許還有更好的辦法解決橫屏問題,有待繼續研究。

  2. 橫屏push橫屏,非官方推薦交互邏輯,一般采用present形式且無push下個頁面的形式,這樣設計有點反人類。

  3. 參考YouTube、騰訊視頻,均采用頁面自刷新形式來更新數據源。這樣可以減少橫屏模式下push方式,減少采坑點。

8.參考:

  1. cocoachina屏幕旋轉
  2. stackoverflow iOS8 force change view controller orientation
  3. 騰訊視頻全屏轉換Demo
  4. different orientation
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容