この記事はレコチョク 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を試す – カルボナーラ街道
この記事を書いた人
最近書いた記事
- 2023.12.14【iOS】ライトニングトークイベントでAI関連の発表をした振り返り
- 2023.07.21【Swift】enumとstatic let(型プロパティ)による定数定義
- 2023.06.27【ChatGPT】ChatGPTでChatGPTチャットボット試作
- 2023.03.27【Swift】アクセスレベルとそれぞれの違い