我這里講解使用的是Masonry,我假設你對約束有一定的了解。
隨着iPhone X的出現,iOS頁面的適配似乎也麻煩了起來,我見得最多的就是通過某種手段判斷機型或者獲取導航欄的高度,然后計算寬高。我不說這種方法好不好,因為它也能解決你目前的問題,但不是我喜歡的方式。
在正式開始之前,我先介紹幾個重要的知識:
1.topLayoutGuide和bottomLayoutGuide

這兩個屬性屬於UIViewController,topLayoutGuide主要就是指導航欄,狀態欄;bottomLayoutGuide主要指TabBar(劉海手機上也可指代底部黑條的部分),主要就是為了讓你使用約束的時候對頂部和底部有個參考,避免視圖上的內容被遮擋。
2.safeAreaLayoutGuide

這個屬性是iOS11才有的,也就是蘋果對於劉海屏給出的一種解決方案。它和1中的兩個屬性作用類似,由於屬於UIView類,不受限於UIViewController,所以在靈活性上更強。
從它的名字可以看出來,它是一種安全區域的參考,那么安全區域指哪塊呢?如下圖:

從上面的圖可以看得出來,安全區域可以保證我們的內容不被遮擋。
3. Masonry中相應的屬性
既然我們用Masonry,我們就需要知道與系統中對應的屬性是哪些:
// 安全區域對應的屬性
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuide NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideLeading NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideTrailing NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideLeft NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideRight NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideTop NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideBottom NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideWidth NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideHeight NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideCenterX NS_AVAILABLE_IOS(11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideCenterY NS_AVAILABLE_IOS(11.0);
// 一般參考對應的屬性
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuide NS_DEPRECATED_IOS(8.0, 11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuide NS_DEPRECATED_IOS(8.0, 11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuideTop NS_DEPRECATED_IOS(8.0, 11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuideBottom NS_DEPRECATED_IOS(8.0, 11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuideTop NS_DEPRECATED_IOS(8.0, 11.0); @property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuideBottom NS_DEPRECATED_IOS(8.0, 11.0);
從名字中我們就能很清晰的識別出來
4.實戰
我們創建一個簡單的工程,初始頁面是一個帶有導航欄的紅色視圖控制器,如下圖所示:

我們將在這上面創建一個綠色的視圖,來具體看一下上面1和2提到的屬性怎么去用。
首先我們不使用上面的屬性來設置約束,代碼如下:
UIView *view = [[UIView alloc] init]; view.backgroundColor = [UIColor greenColor]; [self.view addSubview:view]; [view mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.view.mas_top); make.height.equalTo(@200); make.left.right.equalTo(self.view); }];
我們只是簡單的設置了子視圖和父視圖之間的約束,似乎看起來沒什么問題,但是當我們運行一下就會發現,不好的事情發生了。

我們的綠色視圖竟然被導航欄遮住了一部分,這不是我們所希望了,因為將來有可能遮住我們的重要信息,也許你想着我們可以修改約束中make.top.equalTo(self.view.mas_top)為make.top.equalTo(self.view.mas_top).offset(88),讓其偏移88,但是這樣的壞處是顯而易見的,現在偏移88是沒問題的,但運行在沒有劉海的手機上又要改為偏移64,假如蘋果將來又出個神奇的手機,你是不是又要去判斷手機型號,然后偏移某一個值呢?從現在起,放棄這種適配方法吧(除了個別目的)。
我們把代碼修改為下面這個樣子
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor greenColor];
[self.view addSubview:view];
[view mas_makeConstraints:^(MASConstraintMaker *make) {
if (@available(iOS 11, *)) {
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
} else {
make.top.equalTo(self.mas_topLayoutGuide);
}
make.height.equalTo(@200);
make.left.right.equalTo(self.view);
}];
這個代碼運行出來的效果如下,這樣的效果就是我們想要的,內容不會被導航欄遮擋。注意上面的代碼,有一個if語句,我們判斷了相應API能否在指定平台獲取,而這也是我們適配的關鍵,因為iPhone X以及之后出來的手機,系統肯定是在iOS11之上的(除了個別越獄的),所以我們適配劉海屏的思路就是判斷系統版本就夠了。

我們再來看看底部的適配,先上代碼
UIView *view = [[UIView alloc] init]; view.backgroundColor = [UIColor greenColor]; [self.view addSubview:view]; [view mas_makeConstraints:^(MASConstraintMaker *make) { make.height.equalTo(@200); make.left.right.equalTo(self.view); make.bottom.equalTo(self.view.mas_bottom); }];
這種運行出來的效果為:

也許你認為這樣沒什么不好,你說得對,這樣是沒什么不好,但是底部大約有32的距離是系統不希望我們使用的,因為怕和系統的手勢沖突。所以這里我們也是要用到底部的參考和安全區域的參考
UIView *view = [[UIView alloc] init]; view.backgroundColor = [UIColor greenColor]; [self.view addSubview:view]; [view mas_makeConstraints:^(MASConstraintMaker *make) { make.height.equalTo(@200); make.left.right.equalTo(self.view); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom); // 或者make.bottom.equalTo(self.mas_bottomLayoutGuide); }];
運行出來的效果如下,我們可以看到底部空出來了一段距離,這段距離就是系統不希望我們使用的。

4.總結
通過上面的例子,我們可以看到topLayoutGuide和bottomLayoutGuide與safeAreaLayoutGuide的作用區別不大,但是safeAreaLayoutGuide是在視圖類中就可以使用,更加方便了我們去做適配。通過參考安全區域或者之前的頂部布局參考,我們不在需要去判斷機型,也能達到頁面完美適配的目的。
