この記事はレコチョク Advent Calendar 2022の18日目の記事となります。
はじめに
はじめまして。株式会社レコチョクの長島と申します。
2022年4月に新卒で入社し、6ヶ月の研修期間を経て、今はiOSアプリの開発に携わっています。
音楽はダンスミュージックを中心に、最近は電音部という音楽原案キャラクタープロジェクトの曲をよく聞いています。よろしくお願いします。
現在はOJTとして既存アプリのモックを作成する課題を行っているのですが、その際にSnapKitと呼ばれるライブラリを用い、Interface Builderなしで画面を実装する必要があり、一体SnapKitがどういうものなのかを調べる機会がありました。
今回はそれらを調査して得られた結果として、SnapKitの概要と実装例を記事にしようと思います。
動作環境
- Xcode 14.0.1
- SnapKit 5.6.0
SnapKitについて
概要
SnapKitはUIKitのAuto Layoutを簡単に実装することができるDSL(Domain Specific Language)です。標準の記法として用いられるNSLayoutConstraint
やNSLayoutAnchor
と比較して、制約の記述を簡略化できます。
以下の図はwidth
とheight
が100pxのUIView
を画面の中央に配置したものです。NSLayoutAnchor
とSnapKitそれぞれでの制約の付け方の比較を行いました。
実際のコードを見れば、SnapKitを使用した場合、NSLayoutAnchor
と比べて明らかにコードの記述量が少なくなることがわかります。
// NSLayoutAnchorを利用したコード
override func viewDidLoad() {
super.viewDidLoad()
let subView = UIView()
subView.backgroundColor = .red
view.addSubview(subView)
subView.translatesAutoresizingMaskIntoConstraints = false
let width = subView.widthAnchor.constraint(equalToConstant: 100)
let height = subView.heightAnchor.constraint(equalToConstant: 100)
let centerX = subView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerY = subView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
NSLayoutConstraint.activate([width, height, centerX, centerY])
}
// SnapKitを利用したコード
override func viewDidLoad() {
super.viewDidLoad()
let subView = UIView()
subView.backgroundColor = .red
view.addSubview(subView)
subView.snp.makeConstraints {
$0.size.equalTo(100)
$0.center.equalToSuperview()
}
}
導入方法
今回は導入方法としてSwift Package Managerを利用します。Xcodeのバージョンによっては導入方法が異なる場合があるのでご注意ください。
1.メニューバー上の「File」を開き、「Add Packages…」を押下します。
2.画面右上にある検索バーにSnapKitのGitHub URLを入力し、「Add Package」を押下します。
3.SnapKitのパッケージが追加され、import可能になります。
import SnapKit
実装方法
SnapKitではmakeConstraints()
というメソッドを用い、クロージャの中に制約を記載します。
基本となる書き方は以下のようになります。
<対象View>.snp.makeConstraints {
$0.<対象Viewの属性>.<制約の設定方法>
}
対象Viewの属性には、以下のものが用意されています。
left
right
top
bottom
edges // top, bottom, left, rightの一括設定
leading
trailing
width
height
size // width, heightの一括設定
centerX
centerY
center // centerX, centerYの一括設定
制約の設定方法には、以下のような式が用意されています。
equalTo() // 引数に渡す属性と同じ
equalToSuperview() // 親Viewの属性と同じ
lessThanOrEqualTo() // 引数に渡す属性と同じかそれ以下
lessThanOrEqualToSuperview() // 親Viewの属性と同じかそれ以下
greaterThanOrEqualTo() // 引数に渡す属性と同じかそれ以上
greaterThanOrEqualToSuperview() // 親Viewの属性と同じかそれ以上
実際に上記の式を組み合わせることで、以下のように制約を設定できます。
// 対象Viewのcenterに、親Viewのcenterと同じ位置を設定する制約
<対象View>.snp.makeConstraints {
$0.center.equalToSuperview()
}
これらの記述の後に、offset()
やinset()
を設定できます。
// 対象Viewのedgesに、親Viewのedgesに対して100pxのinsetが追加された位置を設定する制約
<対象View>.snp.makeConstraints {
$0.edges.equalToSuperview().inset(100)
}
また、メソッドチェーンという方法で、複数の属性に同時に制約を設定できます。
<対象View>.snp.makeConstraints {
// 対象Viewのtop・leftに、親Viewのtop・leftに対して100pxのoffsetが追加された位置を設定する制約
$0.top.left.equalToSuperview().offset(100)
// 対象Viewのbottom・rightに、親Viewのbottom・rightに対して50pxのinsetが追加された位置を設定する制約
$0.bottom.right.equalToSuperview().inset(50)
}
実例
以下の図のようなUICollectionViewCell
を作成してみます。
楽曲の情報が入っているUICollectionViewCell
であり、以下のような構造になります。
- 横方向
UIStackView
(Horizontal):hStackView
- 曲ジャケット
UIImageView
:jacketImageView
- 縦方向の楽曲情報格納
UIStackView
(Vertical):infoStackView
- タイトル
UILabel
:titleLabel
- アーティスト
UILabel
:artistLabel
- ハイレゾ
UILabel
:hiResLabel
- タイトル
- メニュー
UIButton
:menuButton
- 曲ジャケット
それぞれの要素を作成し、制約を以下のように設定した場合、このようなコードとなりました。
-
hStackView
-
top
・bottom
に、親Viewのtop
・bottom
と同じ位置を設定する -
left
・right
に、親Viewのleft
・right
に対して16pxのinset
が追加された位置を設定する
-
-
jacketImageView
-
size
を50pxに設定する
-
-
menuButton
-
size
を50pxに設定する
-
final class MusicCell: UICollectionViewCell {
private lazy var hStackView: UIStackView = {
$0.axis = .horizontal
$0.alignment = .center
$0.spacing = 16
return $0
}(UIStackView(arrangedSubviews: [
jacketImageView,
infoStackView,
menuButton
]))
private let jacketImageView: UIImageView = {
$0.contentMode = .scaleAspectFit
return $0
}(UIImageView())
private lazy var infoStackView: UIStackView = {
$0.axis = .vertical
$0.alignment = .leading
$0.distribution = .equalSpacing
return $0
}(UIStackView(arrangedSubviews: [
titleLabel,
artistLabel,
hiResLabel
]))
private let titleLabel: UILabel = {
$0.font = .systemFont(ofSize: 18, weight: .init(rawValue: 0.4))
return $0
}(UILabel())
private let artistLabel: UILabel = {
$0.font = .systemFont(ofSize: 14)
$0.textColor = .darkGray
return $0
}(UILabel())
private let hiResLabel: UILabel = {
$0.font = .systemFont(ofSize: 14)
$0.textColor = .darkGray
return $0
}(UILabel())
private let menuButton: UIButton = {
$0.setImage(.init(named: "more"), for: .normal)
$0.tintColor = .darkGray
return $0
}(UIButton())
override init(frame: CGRect) {
super.init(frame: frame)
// setup()の中で制約を設定する作業を行う
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
addSubview(hStackView)
hStackView.snp.makeConstraints {
$0.top.bottom.equalToSuperview()
$0.left.right.equalToSuperview().inset(16)
}
jacketImageView.snp.makeConstraints {
$0.size.equalTo(50)
}
menuButton.snp.makeConstraints {
$0.size.equalTo(50)
}
}
}
おわりに
SnapKitを用いることで、コードのみで直感的に制約を設定する方法を記載し、実際にUIStackView
との併用を含めたレイアウトを実装しました。
結果として、以下のような特徴があることがわかりました。
-
makeConstraints()
を用いることで、クロージャの中に制約をまとめて記載できる -
offset
やinset
を上記の式に続ける形で記載できる - メソッドチェーンで複数の属性に一度に制約を記載できる
今回の記事を見て、SnapKitの概要と基本的な実装に関して理解が進みましたら幸いです。
明日のレコチョク Advent Calendarは19日目【Swift】Widgetの作り方 〜iOS 16対応版〜
です。お楽しみに!
参考資料
- SnapKit/SnapKit: A Swift Autolayout DSL for iOS & OS X - GitHub
- SnapKit Docs - GitHub
- 【Swift4】SnapKitがめちゃくちゃ便利だった件 - RIGHTCODE
- SnapKitを使ってみた。 - Qiita
- 逆引きSnapKit - AutoLayoutをコードからサクッと設定しよう - Qiita
- SnapKitを試す - カルボナーラ街道
この記事はレコチョクのエンジニアブログの記事を転載したものとなります。