觸手iPhoneX適配實戰

小火爐發表於2017-11-01

題外話

網上適配的帖子特別多,大多數都是官方教程的中文版。這篇文章主要記錄觸手TVAPP在適配中遇到的問題。
這是一次公司內部技術分享會的內容,內容共分為三個部分:

  • Xcode9新特性
  • iOS 11 適配
  • iPhone X適配

這是第三部分。這裡我不在重複前兩部分的內容,用到的知識點會直接使用,比如safeAreaInsets。今天的適配預設已經適配iOS 11

第一部分:Xcode9新特性
第二部分:iOS 11 適配
言歸正傳

簡單介紹 iPhone X

ScreenSize

iPhone X具有超視網膜屏。邏輯解析度為375pt X 812pt。螢幕尺寸5.8英寸,解析度為1125 x 2436。四個角有圓角、頂部有齊劉海(感測器槽)、底部有HomeIndicator
StatusBar導航欄高度44pttabBar高度為83pt.

ScreenSize
ScreenSize

介面呈現最佳實踐

  • 保持頁面元素在安全區域內。
  • 佈局元素不被圓角切割。
  • 佈局元素不被齊劉海遮擋。
  • 佈局元素不佔用statusBarstatusBar部分有供系統使用的邊緣手勢。
  • 有互動的控制元件不被HomeIndicator重疊。

如下圖

超過安全區域
超過安全區域

statusBar佔用
statusBar佔用

與HomeIndicator重疊
與HomeIndicator重疊

statusBar手勢
statusBar手勢

橫屏佈局
橫屏佈局

開始適配工作

先看看未適配前執行的結果

  1. 啟動後,沒有充滿螢幕,上下部分有黑邊。
  2. 中間的 +號按鈕下沉。

    首頁
    首頁

  3. “我的”頁面內容下沉。
    這個問題屬於iOS 11適配問題,上一篇我們講過,這裡不贅述。

    我的頁面
    我的頁面

先把第一個問題解決了。因為iPhone X的解析度問題,APP在啟動的時候,沒有相應的圖片資源。解決這個問題只需對LaunchImage新增1125 x 2346啟動屏圖片。或者啟動項從Launch Images Source->asset變更為LaunchScreenFile。指定為LaunchScreenxib或者.storyboard

執行再看效果:

首頁重疊
首頁重疊

發現問題:

  1. 頂部已經與statusBar重疊了。
    嘗試點選,按鈕上半部分不響應事件。statusBar的View在頂部,事件被statusBar截獲了。這種方式也不符合UI規範。
  2. 其他頁面基本都是這樣的問題。
  3. 中間按鈕在push到其他頁面回來後,偶爾會往下掉。

在上一篇我們講了一般使用IUNavigationBar的三種方式。觸手使用的是第三種--完全自定義的view。因為觸手TV的介面風格里,很多頁面都是首頁這個樣式的,頂部是tab樣式的,頁面是聯動的。所以我們有專門的一個控制元件來封裝這個風格的頁面。所以很久以前的工程裡為了方便,直接棄用了系統的NavigationBar
做出修改之前,我們先來看看整個工程的view層級關係:

原來的APP結構
原來的APP結構

從結構中可以看出,整個APP裡共用同一個NavigationController,這種做飯,有缺點也有優點,優點就是針對未適配之前的頁面,隱藏導航欄,在源頭就可以直接設定。在需要push的地方,除極個別被modal出來的Controller,直接可用self.navigationController獲得導航屬性。缺點就是不能針對定製頁面控制導航欄的顯示與影藏。可能你會想在viewWillAppearViewWillDisappear裡去控制隱藏和顯示。考慮到多個檢視控制器的執行時序先後對導航欄進行設定,結果就不一定是我們想要的了。
為了更好的使用系統導航欄,又能離散的控制導航欄的隱藏,我們將APP的結構改為這樣。

修改後的APP結構
修改後的APP結構

顯而易見這樣的結構更合理一些。我個人比較推薦使用底部tab形式的UI,都使用這樣的結構。

準備工作做完了,開始適配導航欄。
在原來的MainViewController:UITabBarcontroller裡有這麼一段程式碼

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:YES animated:YES];
}複製程式碼

這段程式碼就是在源頭把整個APP裡的導航欄隱藏的關鍵程式碼。現在可以直接刪掉。
在基類的ViewControoler裡,新增bool屬性hiddenNavigationBar。在需要隱藏導航欄的頁面,設定為true
NavigationController的基類裡實現UINavigationControllerDelegate方法,根據控制器的hiddenNavigationBar屬性,是否隱藏導航欄。不建議在viewWillAppearviewWillDisappear設定,這種方式在視覺上的表現不是太好,也不怎麼優雅。

- (void)navigationController:(UINavigationController*)navigationController willShowViewController:(UIViewController*)viewController animated:(BOOL)animated {

    if([viewController isKindOfClass:[CSViewController class]]){
        CSViewController *vc = (CSViewController *)viewController;
        if (vc.navigationBarHidden) {
            [navigationController setNavigationBarHidden:YES animated:YES];
        } else {
            if (navigationController.navigationBarHidden) {
                [navigationController setNavigationBarHidden:NO animated:YES];
            }
        }
    }
}複製程式碼

我們決定使用系統的導航欄,主要原因是為了減少工作量。在原來的頁面裡,每個xib頁面維護了一個自定義的導航欄,這次改動,每個xib檔案都需要改一遍,為了一勞永逸的解決這個問題。我們覺得直接向原生控制元件靠攏,把適配的工作交給系統來完成。其實工作量也沒多少,把每個頁面的導航欄刪掉就可以了。執行後效果如下:

導航示例
導航示例

針對於上面提出的中間按鈕下沉問題。我們先看看iPhone X上tabBar的View層級關係。

tabBar
tabBar

高度是83,下面預留了35給虛擬home鍵。所以原先的直接新增到UITabBarController,設定frame就有了偏差,為了佈局,使用了一個空的TabBarItem作為佔位用。

buttonCenter = [[UIButton alloc] initWithFrame:CGRectMake(kScreenWidth / 2 - 30, kScreenHeight - 60 - offSety, 60, 60)];
    [buttonCenter setImage:[UIImage imageNamed:@"ic_main_center.png"] forState:UIControlStateNormal];
    [buttonCenter setImage:[UIImage imageNamed:@"ic_main_center_p.png"] forState:UIControlStateHighlighted];
    [buttonCenter addTarget:self action:@selector(clickedButtonCenter) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:buttonCenter];複製程式碼

解決這個問題,可以在原來的基礎上針對iPhone X適配frame。我們沒有這麼做,而是選擇繼承UITabbar,重寫layoutSubviews。順便把很多運營需要的邏輯紅點也剝離到這裡。核心程式碼如下

- (void)layoutSubviews {
    [super layoutSubviews];
    Class class = NSClassFromString(@"UITabBarButton");
    CGFloat width = self.width / BarItemCount;
    for (UIView *view in self.subviews) {

    //**
    plusButton 就是需要中間的按鈕
    */

        if ([view isEqual:self.plusButton]) {
            view.frame = CGRectMake(0, 0, width, width);
            view.center = CGPointMake(self.width / 2,BarItemCenterY);
        } else if ([view isKindOfClass:class]){
            //如果是系統的UITabBarButton,那麼就調整子控制元件位置,空出中間位置
            CGRect frame = view.frame;
            NSInteger index = view.origin.x / width;
            NSInteger originIndex = index;
            if (index >= (BarItemCount - 1) / 2) {
                index++;
            }
            CGFloat x = index * width;
            view.frame = CGRectMake(x, view.frame.origin.y, width, frame.size.height);
            }
        }
    }
    [self bringSubviewToFront:self.plusButton];
}複製程式碼

在事件響應鏈裡截獲點選事件,對超出tabBar的部分支援事件的響應

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //這一個判斷是關鍵,不判斷的話push到其他頁面,點選發布按鈕的位置也是會有反應的,這樣就不好了
    //self.isHidden == NO 說明當前頁面是有tabbar的,那麼肯定是在導航控制器的根控制器頁面
    //在導航控制器根控制器頁面,那麼我們就需要判斷手指點選的位置是否在釋出按鈕身上
    //是的話讓釋出按鈕自己處理點選事件,不是的話讓系統去處理點選事件就可以了
    if (self.isHidden == NO) {
        //將當前tabbar的觸控點轉換座標系,轉換到釋出按鈕的身上,生成一個新的點
        CGPoint newP = [self convertPoint:point toView:self.plusButton];
        CGPoint extendPoint = [self convertPoint:point toView:self.extendView];
        //判斷如果這個新的點是在釋出按鈕身上,那麼處理點選事件最合適的view就是釋出按鈕
        if ( [self.plusButton pointInside:newP withEvent:event]) {
            return self.plusButton;
        } else {//如果點不在釋出按鈕身上,直接讓系統處理就可以了
            return [super hitTest:point withEvent:event];
        }
    }
    else {//tabbar隱藏了,那麼說明已經push到其他的頁面了,這個時候還是讓系統去判斷最合適的view處理就好了
        return [super hitTest:point withEvent:event];
    }
}複製程式碼

目前為止,我們已經解決了上面提出來的問題。
在看看其他頁面,發現有幾個問題。
動態詳情評論框與home鍵區域重疊了。

動態詳情評論框
動態詳情評論框

直播頁面的彈幕框也是這個問題。

直播頁面橫屏下按鈕排布問題。
直播橫屏
直播橫屏

這些只需要基本都是UED的範疇了,對特定的約束進行調整,使其不遮擋home鍵。
有了第一部分:Xcode9新特性的介紹和第二部分:iOS 11 適配。iPhone X的適配就簡單多了。

使用safeAreaInsets

通篇我們都沒有用到safeAreaInsets來進行適配,是應為在IB介面裡,safeAreaInsets支援的最低版本為iOS 9.0,我們還需要相容之前的版本。所以這裡沒有介紹這種方法。
如果需要使用safeAreaInsets,那麼在IB檔案開啟右側第一個選單項showfileinspector,勾選User Safe Area Layout Guides

user safe area
user safe area

在設定約束時,請選擇safe area作為secondItem。這樣就可以很好的適配所有機型,保證不同機型下,都能做到官方推薦的佈局規範了。
safe area layout
safe area layout

相關文章