はじめに
SwiftUIの登場でAutoLayoutの出番は少なくなったものの、業務ではまだまだAutoLayoutを使用する機会も多いのではないでしょうか?
AutoLayoutと言えばStoryboardを思い浮かべる人も多いと思います。しかし、もっさりした動作のStoryboard(Interface builder)をわざわざ開かなくとも、AutoLayoutを設定することができます。SnapKitならめちゃめちゃ簡単に!
SnapKitを使ったサンプルコード
view1とview2を同じ大きさにして左揃えで縦に並べる例
SnapKitなし
guard let superview = view1.superview else {
return
}
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view1.widthAnchor.constraint(equalToConstant: 200),
view1.heightAnchor.constraint(equalToConstant: 50),
view1.topAnchor.constraint(equalTo: superview.topAnchor, constant: 50),
view1.leftAnchor.constraint(equalTo: superview.leftAnchor, constant: 50),
view2.widthAnchor.constraint(equalTo: view1.widthAnchor),
view2.heightAnchor.constraint(equalTo: view1.heightAnchor),
view2.leftAnchor.constraint(equalTo: view1.leftAnchor),
view2.topAnchor.constraint(equalTo: view1.bottomAnchor, constant: 16)
])
SnapKitあり
view1.snp.makeConstraints { make in
make.size.equalTo(CGSize(width: 200, height: 50))
make.top.left.equalTo(50)
}
view2.snp.makeConstraints { make in
make.left.size.equalTo(view2)
make.top.equalTo(view2.snp.bottom).offset(16)
}
いかがでしょうか?SnapKitを使うことで書き味や可読性が、グッと向上していることがおわかりになると思います。ちなみに2020.07現在、SnapKitはGitHubで1万6千スターを獲得しています。(Storyboard嫌いの多さにビックリ!)
本記事では、SnapKitの使い方を逆引きリファレンス形式で紹介していきます。
動作確認環境
- Xcode 11.6
- Swift 5
- SnapKit 5.0.1
まずは基本から
makeConstraintsメソッドのクロージャ内で制約を設定する
// SnapKitをインポートすると、UIViewにsnpというプロパティが生える
import SnapKit
// snpプロパティのmakeConstraintsメソッドの引数のクロージャ内で、制約を設定する
view.snp.makeConstraints { make in
// view.top = otherView.top
make.top.equalTo(otherView.snp.top)
// view.left = otherView.right + 8
make.left.equalTo(otherView.snp.right).offset(8)
}
これ以降、make変数が出てきた場合は、makeConstraintsのクロージャ内での記述です。
同名のプロパティに対する制約を設定するときは、引数にUIView/UILayoutGuideを渡すことができる
// make.top.equalTo(otherView.snp.top)と同じ
make.top.equalTo(otherView)
複数のプロパティをチェーンで設定できる
// make.top.equalTo(otherView); make.bottom.equalTo(otherView)と同じ
make.top.bottom.equalTo(otherView)
親viewに対する制約のときは専用のメソッドが用意されている
make.top.equalToSuperview()
make.top.lessThanOrEqualToSuperview()
make.top.greaterThanOrEqualToSuperview()
幅と高さには定数を指定できる
// width = 100
make.width.equalTo(100)
// height <= 200
make.height.lessThanOrEqualTo(200)
// width >= 400 && height >= 400
make.size.greaterThanOrEqualTo(CGSize(width: 400, height: 400))
幅と高さ以外に定数を指定すると、親ビューに対する制約になる
// left <= superview.left + 10
// make.left.lessThanOrEqualToSuperview().offset(10)と同じ
make.left.lessThanOrEqualTo(10)
逆引きリファレンス
制約の優先度を設定する
make.top.equalTo(otherView).priority(600)
make.top.equalTo(otherView).priority(.low) // = 250
make.top.equalTo(otherView).priority(.medium) // = 500(OSXの場合は501)
make.top.equalTo(otherView).priority(.high) // = 750
make.top.equalTo(otherView).priority(.required) // = 1000
制約にラベルを付ける
AutoLayoutの警告ログやDebug View Hierarchy画面に表示されます
make.top.equalTo(otherView).labeled("viewTopConstraintToOtherView")
上下左右(top/bottom/left/right)の制約をまとめて設定する
// make.top.bottom.left.right.equalTo(otherView)と同じ
make.edges.equalTo(otherView)
// Insetsを指定する場合
// ConstraintInsetsはUIEdgeInsetsのaliasなので、UIEdgeInsetsも使用可
make.edges.equalTo(otherView).inset(ConstraintInsets(top: 8, left: 16, bottom: 8, right: 16))
// 親ビューに対するinsetsなら直接渡せる
// make.edges.equalToSuperView().inset(...)と同じ
make.edges.equalTo(UIEdgeInsets(top: 32, left: 12, bottom: 32, right: 12))
幅と高さに相対的な制約を設定する
// width = otherView.width * 2 + 50
make.width.equalTo(otherView).multipliedBy(2).offset(50)
// height = superview.height / 10
make.height.equalToSuperview().dividedBy(10)
サイズ(幅と高さ)の制約をまとめて設定する
make.size.equalTo(CGSize(width: 100, height: 100))
// width = otherView.width - 50, height = otherView.height - 50
make.size.equalTo(otherView).offset(-50)
中央の制約を設定する
// centerX = otherView.centerX + 10
make.centerX.equalTo(otherView).offset(10)
make.centerY.equalTo(otherView).offset(10)
// 上の2つと同じ
make.center.equalTo(otherView).offset(10)
SafeAreaに対する制約を設定する
guard let guide = view.rootSafeAreaLayoutGuide else {
return
}
view.snp.makeConstraints { make in
make.edges.equalTo(guide)
}
...
extension UIView {
var rootSafeAreaLayoutGuide: UILayoutGuide? {
var rootView: UIView? = self
while rootView?.superview != nil {
rootView = rootView?.superview
}
return rootView?.safeAreaLayoutGuide
}
}
Widthを割合で指定しつつ、最大値を設定する
// 1. 親ビューの幅の半分に設定する
make.width.equalToSuperview().dividedBy(2).priority(.high)
// 2. 1.の制約より優先度を上げて最大値を指定する
make.width.lessThanOrEqualTo(100).priority(.required)
アスペクト比を保ったまま、親ビューの中で最大限大きく表示する
// width : height = 2 : 1のアスペクト比に設定する
make.width.equalTo(inner.snp.height).multipliedBy(2).priority(.required)
// 親ビューのサイズを超えないように設定する
make.size.lessThanOrEqualToSuperview().priority(.required)
// 上記より優先度を下げて、幅と高さを親ビューと同じに設定する
make.width.equalToSuperview().priority(.high)
make.height.equalToSuperview().priority(.high)
// 中央に表示する
make.center.equalToSuperview()
制約の参照を保持する
let topConstraint: Constraint
view.snp.makeConstraints { make in
// constraintプロパティを呼び出す。この制約は自動的にactivateされる
topConstraint = make.top.equalToSuperview().offset(8).constraint
}
// 制約をdeactivate
topConstraint.deactivate()
// 制約をactivate
topConstraint.activate()
// Offsetを更新
topConstraint.update(offset: 16)
アニメーションに制約を使う
let heightConstraint: Constraint
view.snp.makeConstraints { make in
heightConstraint = make.height.equalTo(100).offset(0).constraint
}
UIView.animate(withDuration: 1, animations: {
self.heightConstraint?.update(offset: 200)
self.layoutIfNeeded()
})
UIView#updateConstraints, UIViewController#updateViewConstraints内で使用する
// updateConstraintsメソッドは制約のconstantだけを更新する
view.snp.updateConstraints { make in
make.height.equalTo(self.height)
}
既存の制約を破棄する
view.snp.removeConstraints()
既存の制約を破棄して、新しい制約を設定する
@objc func changeHeight(sender: UIButton) {
// remakeConstraintsメソッドは既存の制約を破棄するとともに、新しい制約を設定する
snp.remakeConstraints { make in
make.height.equalTo(sender.tag)
make.top.left.equalToSuperview()
make.width.equalTo(100)
}
}