題外話
網上適配的帖子特別多,大多數都是官方教程的中文版。這篇文章主要記錄觸手TV
APP在適配中遇到的問題。
這是一次公司內部技術分享會的內容,內容共分為三個部分:
- 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
導航欄高度44pt
。tabBar
高度為83pt
.
介面呈現最佳實踐
- 保持頁面元素在安全區域內。
- 佈局元素不被圓角切割。
- 佈局元素不被齊劉海遮擋。
- 佈局元素不佔用
statusBar
。statusBar
部分有供系統使用的邊緣手勢。 - 有互動的控制元件不被
HomeIndicator
重疊。
如下圖
開始適配工作
先看看未適配前執行的結果
- 啟動後,沒有充滿螢幕,上下部分有黑邊。
中間的
+
號按鈕下沉。“我的”頁面內容下沉。
這個問題屬於iOS 11適配問題,上一篇我們講過,這裡不贅述。
先把第一個問題解決了。因為iPhone X的解析度問題,APP在啟動的時候,沒有相應的圖片資源。解決這個問題只需對LaunchImage
新增1125 x 2346
啟動屏圖片。或者啟動項從Launch Images Source
->asset變更為LaunchScreenFile
。指定為LaunchScreen
的xib
或者.storyboard
。
執行再看效果:
發現問題:
- 頂部已經與
statusBar
重疊了。
嘗試點選,按鈕上半部分不響應事件。statusBar
的View在頂部,事件被statusBar
截獲了。這種方式也不符合UI規範。 - 其他頁面基本都是這樣的問題。
- 中間按鈕在push到其他頁面回來後,偶爾會往下掉。
在上一篇我們講了一般使用IUNavigationBar
的三種方式。觸手使用的是第三種--完全自定義的view。因為觸手TV
的介面風格里,很多頁面都是首頁這個樣式的,頂部是tab樣式的,頁面是聯動的。所以我們有專門的一個控制元件來封裝這個風格的頁面。所以很久以前的工程裡為了方便,直接棄用了系統的NavigationBar
。
做出修改之前,我們先來看看整個工程的view層級關係:
從結構中可以看出,整個APP裡共用同一個NavigationController,這種做飯,有缺點也有優點,優點就是針對未適配之前的頁面,隱藏導航欄,在源頭就可以直接設定。在需要push的地方,除極個別被modal出來的Controller,直接可用
self.navigationController
獲得導航屬性。缺點就是不能針對定製頁面控制導航欄的顯示與影藏。可能你會想在viewWillAppear
和ViewWillDisappear
裡去控制隱藏和顯示。考慮到多個檢視控制器的執行時序先後對導航欄進行設定,結果就不一定是我們想要的了。為了更好的使用系統導航欄,又能離散的控制導航欄的隱藏,我們將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
屬性,是否隱藏導航欄。不建議在viewWillAppear
和viewWillDisappear
設定,這種方式在視覺上的表現不是太好,也不怎麼優雅。
- (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層級關係。
高度是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
在設定約束時,請選擇
safe area
作為secondItem
。這樣就可以很好的適配所有機型,保證不同機型下,都能做到官方推薦的佈局規範了。