通常我們在適配異形屏的時候,我們可能會使用 safeAreaInsets。使用時機不對的話,safeAreaInsets 的值還會存在問題。或許你可以使用 key window 的 safeAreaInsets ,亦或者你可以通過重寫 func safeAreaInsetsDidChange() 方法,在合適的時候來修改布局,但這些操作總是比較麻煩,用起來并不舒服。
有沒有更好的方式呢???我們先來介紹兩個屬性。
layoutMargins
The default spacing to use when laying out content in the view.
iOS 8 新增,通過屬性名,我們就了解他是什么了,簡單來說就是布局中的邊距。

layoutMarginsGuide
A layout guide representing the view’s margins.
iOS 9 新增,你可以通過鏈接查看更多相關(guān)信息。
如何使用
下面將用過三個用例來總結(jié)用法。
示例一
let pinkView = UIView()
pinkView.backgroundColor = .systemPink
pinkView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pinkView)
view.addConstraints([
NSLayoutConstraint(
item: pinkView,
attribute: .leftMargin,
relatedBy: .equal,
toItem: view,
attribute: .leftMargin,
multiplier: 1,
constant: 0
),
NSLayoutConstraint(
item: pinkView,
attribute: .rightMargin,
relatedBy: .equal,
toItem: view,
attribute: .rightMargin,
multiplier: 1,
constant: 0
),
NSLayoutConstraint(
item: pinkView,
attribute: .topMargin,
relatedBy: .equal,
toItem: view,
attribute: .topMargin,
multiplier: 1,
constant: 0
),
NSLayoutConstraint(
item: pinkView,
attribute: .bottomMargin,
relatedBy: .equal,
toItem: view,
attribute: .bottomMargin,
multiplier: 1,
constant: 0
)
])
view.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
可以使用 SnapKit 來簡化下代碼:
let pinkView = UIView()
pinkView.backgroundColor = .systemBlue
pinkView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pinkView)
pinkView.snp.makeConstraints {
$0.edges.equalTo(self.view.layoutMarginsGuide)
}
layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
self.view.layoutMarginsGuide 還可以替換成 self.view.snp.margins ,兩種方式等價。
同時,SnapKit 也可以單獨控制四個邊距,使用 leftMargin 、rightMargin、topMargin、bottomMargin 單獨控制。


可以從上面的圖片中看到,雖然我們設置四個邊距都是20pt。但是,實際在不同的機型上面的顯示,我們?nèi)庋劭梢姷倪吘嗍遣灰粯拥?,橫豎屏也是不一樣的。
這里就有必要提一下安全區(qū)域了,我們可以看到pinkView的視圖完全顯示在安全區(qū)域內(nèi)。事實上我們在設置布局的代碼時,并沒有考慮各種情況的安全區(qū)域,但是系統(tǒng)就是為我們加上了。我想,到這里,這種布局的好用之處就不言而喻了。
用例二
我們經(jīng)常會遇到在頁面底部添加一個工具條的需求,這個工具條需要做異形屏的適配。也就是在異形屏上,將其底部增加留白,使操作相關(guān)元素處在安全區(qū)域內(nèi)。
我們可以這樣來布局,達到適配的目的:
class BottomBar: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layoutMargins = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
addSubview(button)
button.snp.makeConstraints {
$0.width.equalTo(90)
$0.height.equalTo(36)
$0.right.equalTo(self.snp.rightMargin)
$0.top.equalTo(self.snp.topMargin)
$0.bottom.equalTo(self.snp.bottomMargin)
}
}
...
}
class ViewController: UIViewController {
let bottomView = BottomBar()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(bottomView)
bottomView.snp.makeConstraints {
$0.left.bottom.right.equalTo(0)
}
}
}


可以看到底部工具條已經(jīng)適配好了,不需要我們做其他的操作??????。
上面的代碼,是通過一個尺寸固定的 button 將底部工具條撐滿,我們將 button 的底部約束設置成 $0.bottom.equalTo(self.snp.bottomMargin) ,設置容器視圖的 layoutMargins.bottom = 15 ,實際效果圖上面,系統(tǒng)已經(jīng)為我們自動加上了safeAreaInsets.bottom 。同時,橫屏狀態(tài)下,底部和右邊都加上了安全距離??????。
用例三
在用例二的基礎上,我們再加上一個工具條。
view.addSubview(bottomView)
bottomView.snp.makeConstraints {
$0.left.bottom.right.equalTo(0)
}
let bottomView = BottomBar()
view.addSubview(bottomView)
bottomView.snp.makeConstraints {
$0.left.right.equalTo(0)
$0.bottom.equalTo(self.bottomView.snp.top).offset(-1)
}

明顯,我們看到上面那個工具條的底部沒有加上 safeAreaInsets.bottom ,但是右邊加上了 safeAreaInsets.right 。
到這里,我們可以得出總結(jié):
當視圖的任意邊跟屏幕的邊緣相交時,使用 layoutMarginsGuide 布局,系統(tǒng)會給相應的邊的邊距加上安全區(qū)域的邊距。
另外,我們可以在后續(xù)的使用中來動態(tài)調(diào)整 layoutMargins 的值,調(diào)整后,視圖會實時刷新相應邊距,甚至你可以給這個變化加上動畫。
是不是很Nice???
總結(jié)
這種布局方式,還是非常推薦使用的,通過上面的例子,我們就可以體會到它的妙處。在這個過程中,我們不需要考慮 safeAreaInsets ,僅僅只需要理解 layoutMargins 和 layoutMarginsGuide,并正確的使用即可。
本文只是簡單介紹了 layoutMargins 和 layoutMarginsGuide 的一部分使用,算是拋磚引玉。關(guān)于它的使用,我想只有你真正使用起來,你才會覺得這樣的設計的好處。
值得注意的是,在 iOS 11 推出了 directionalLayoutMargins ,也就是 layoutMargins 的替代物,使用起來并沒有大的差別,僅僅是換了個枚舉而已,感興趣的可以自己去試下。關(guān)于布局還有很多內(nèi)容值得研究,正確的使用系統(tǒng)提供的方法,可以使我們寫出更健壯的代碼,同時可以讓我們很好的適配不同的屏幕,和不同的設備。
如果這篇文章對你有幫助,不妨隨手點個贊!謝謝??
??原創(chuàng),禁止未授權(quán)轉(zhuǎn)載,只接收鏈接轉(zhuǎn)載,不接受內(nèi)容拷貝轉(zhuǎn)載!