- 檢索詞條:safeAreaInsets iOS 11 安全距離 適配iPhoneX viewSafeAreaInsetsDidChange iOS 11安全距離什么時候被改變
- 環(huán)境 iOS 11 Xcode9.0 以上
(這里也會介紹如果你和你的同事兩個人的Xcode一個是9.0以上 一個是9.0以下情況的解決辦法)
iOS 7 之后蘋果給 UIViewController 引入了 topLayoutGuide 和 bottomLayoutGuide 兩個屬性來描述不希望被透明的狀態(tài)欄或者導(dǎo)航欄遮擋的最高位置(status bar, navigation bar, toolbar, tab bar 等)。這個屬性的值是一個 length 屬性( topLayoutGuide.length)。 這個值可能由當前的 ViewController 或者 NavigationController 或者 TabbarController 決定。
- 一個獨立的ViewController,不包含于任何其他的ViewController。如果狀態(tài)欄可見,topLayoutGuide表示狀態(tài)欄的底部,否則表示這個ViewController的上邊緣。
- 包含于其他ViewController的ViewController不對這個屬性起決定作用,而是由容器ViewController決定這個屬性的含義:
- 如果導(dǎo)航欄(Navigation Bar)可見,topLayoutGuide表示導(dǎo)航欄的底部。
- 如果狀態(tài)欄可見,topLayoutGuide表示狀態(tài)欄的底部。
- 如果都不可見,表示ViewController的上邊緣。
這部分還比較好理解,總之是屏幕上方任何遮擋內(nèi)容的欄的最底部。
iOS 11 開始棄用了這兩個屬性, 并且引入了 Safe Area 這個概念。蘋果建議: 不要把 Control 放在 Safe Area 之外的地方
// These objects may be used as layout items in the NSLayoutConstraint API
@available(iOS, introduced: 7.0, deprecated: 11.0)
open var topLayoutGuide: UILayoutSupport { get }
@available(iOS, introduced: 7.0, deprecated: 11.0)
open var bottomLayoutGuide: UILayoutSupport { get }
那么今天我們就來研究一下這個iOS 11的新 API
UIView 中的 safe area
- iOS 11 中 UIViewController 的 topLayoutGuide 和 bottonLayoutGuide 兩個屬性被 UIView 中的 safe area
替代了。
@available(iOS 11.0, *)
open var safeAreaInsets: UIEdgeInsets { get }
@available(iOS 11.0, *)
open func safeAreaInsetsDidChange()
safeAreaInsets
這個屬性表示相對于屏幕四個邊的間距, 而不僅僅是頂部還有底部。這么說好像沒有什么感覺, 我們來看一看這個東西分別在 iPhone X 和 iPhone 8 中是什么樣的吧!
什么都沒有做, 只是新建了一個工程然后在 Main.storyboard 中的 UIViewController 中拖了一個淺藍色的 View 并且設(shè)置約束為:

運行結(jié)果:
iPhone 8 VS iPhone X Safe Area (豎屏)

iPhone 8 VS iPhone X Safe Area (橫屏)

這樣對比可以看出, iPhone X 同時具有上下, 還有左右的 Safe Area。
注意
安全距離改變時機
- 這里有一個坑要踩踩了
一開始筆者也是在這個坑了踩了很久 還以為蘋果搞個安全距離的概念來忽悠我們的如果我們在ViewController- (void)viewDidLoad來打印self.view.safeAreaInsets會發(fā)現(xiàn)始終 顯示self.view.safeAreaInsets = {0, 0, 0, 0}這里有一個新的APIviewSafeAreaInsetsDidChange調(diào)用順序 也就是我們之前所了解到的控制器View的生命周期的調(diào)用順序(這里只列出View被加載時的順序)
viewDidLoad
viewWillAppear
viewSafeAreaInsetsDidChange
? UIEdgeInsets - top : 44.0 - left : 0.0 - bottom : 34.0 - right : 0.0 // 在iPhoneX 中豎屏模式下的顯示效果
viewWillLayoutSubviews
viewDidAppear
只有在調(diào)用viewSafeAreaInsetsDidChange及以后的方法才能獲得view和Controller的UIEdgeInsets。所以在viewDidLoad中根據(jù)Safe Area設(shè)置界面會有問題。
橫豎屏狀態(tài)下的安全距離
-
明白了安全距離的改變時機我們再來說說iPhoneX以下的機型 在 iOS 11中的安全距離問題
筆者發(fā)現(xiàn)iPhone8在豎屏下 其頂部的安全距離 == 狀態(tài)欄的高度在橫屏下四個反向的安全距離均為0
Snip20171104_7.png 同樣的我們來看看iPhoneX 中的橫豎屏情況

通過打印 我們驗證了之前說的 iPhoneX 在
左右方向上也是有安全距離的 而且距離為 21當然這種距離只有在iPhoneX 在橫屏的模式下才會出現(xiàn) 在豎屏模式下其左右的安全距離為0
筆者心得
筆者在開發(fā)中由于要自定義導(dǎo)航欄 又不想麻煩準確的說是習慣了自己之前的開發(fā)方式會在viewDidLoad中來布局自定義的導(dǎo)航條 而在以上的講解中 我們又在viewDidLoad中拿不到安全距離準確的說這個時候安全距離還沒有改變這個時候要布局導(dǎo)航欄時 我們可以使用獲取系統(tǒng)狀態(tài)欄高度的方式來獲得頂部安全距離 這個時候我們在布局反面就輕松多了 筆者的APP只不支持旋轉(zhuǎn)屏 只支持豎屏一個反向 狀態(tài)欄宏定義拿走不謝
/** 獲取狀態(tài)欄高度 */
#define kStatusH MIN([UIApplication sharedApplication].statusBarFrame.size.height, [UIApplication sharedApplication].statusBarFrame.size.width)
示例:
+ (instancetype)redEnvelopeListNavView{
YBSRedEnvelopeListNavView *navView = [[YBSRedEnvelopeListNavView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, kStatusH + 44)]; // 狀態(tài)欄的高度始終為44 從未改變過 只是在iPhoneX 中 狀態(tài)欄的高度為44 其他的為20
navView.backgroundColor = [UIColor whiteColor];
// 這個時候在左側(cè)增加一個按鈕
// 返回鍵
YBS_BaseButton *popBtn = [YBS_BaseButton baseButtonNormaImage:@"taba_2_001" selectImage:@"taba_2_001" target:navView action:@selector(clickPopBtn:) normalTitleColor:nil selectTitleColor:nil titleEdgeInsets:UIEdgeInsetsZero titleFont:0 normalTitle:nil selectTitle:nil];
popBtn.size = CGSizeMake(navView.height - kStatusH , navView.height - kStatusH);
popBtn.left = 5;
popBtn.top = kStatusH; // 按鈕的頂部從 狀態(tài)欄的底部開始布局
[navView addSubview:popBtn];
return navView;
}
到這里你應(yīng)該知道 安全距離的改變時機 安全距離會應(yīng)該橫豎屏的不同而改變原來為0 的可能會變?yōu)榇笥? 原來大于0的可能會變?yōu)? 在iPhoneX上以及在其他機型上安全距離上是不相等的
最后如果你也像筆者這樣需要自定義導(dǎo)航欄 采用筆者的方法 會比較靈活 狀態(tài)欄的高度在任何地方都可以拿到 如筆者就是定義了個導(dǎo)航類 在需要的控制器中直接addSubview就好了 當然你也可以在viewDidLoad定義 在viewSafeAreaInsetsDidChange及以后的方法中布局
