目次

目次

【iOS】SnapKitの概要と実装例

アバター画像
長島大和
アバター画像
長島大和
最終更新日2022/12/18 投稿日2022/12/18

この記事はレコチョク 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)です。標準の記法として用いられる NSLayoutConstraintNSLayoutAnchorと比較して、制約の記述を簡略化できます。

以下の図は widthheightが100pxのUIViewを画面の中央に配置したものです。NSLayoutAnchorとSnapKitそれぞれでの制約の付け方の比較を行いました。 実際のコードを見れば、SnapKitを使用した場合、 NSLayoutAnchorと比べて明らかにコードの記述量が少なくなることがわかります。

SnapKitSample
// 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…」を押下します。

InstallingSnapKit1

2.画面右上にある検索バーにSnapKitのGitHub URLを入力し、「Add Package」を押下します。

InstallingSnapKit2

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を作成してみます。

SnapKitCellSample.png

楽曲の情報が入っている UICollectionViewCellであり、以下のような構造になります。

SnapKitCellSample2.png
  • 横方向UIStackView(Horizontal): hStackView
    • 曲ジャケットUIImageView: jacketImageView
    • 縦方向の楽曲情報格納UIStackView(Vertical): infoStackView
      • タイトルUILabel: titleLabel
      • アーティストUILabel: artistLabel
      • ハイレゾUILabel: hiResLabel
    • メニューUIButton: menuButton

それぞれの要素を作成し、制約を以下のように設定した場合、このようなコードとなりました。

  • hStackView
    • topbottomに、親Viewのtopbottomと同じ位置を設定する
    • leftrightに、親Viewのleftrightに対して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()を用いることで、クロージャの中に制約をまとめて記載できる
  • offsetinsetを上記の式に続ける形で記載できる
  • メソッドチェーンで複数の属性に一度に制約を記載できる

今回の記事を見て、SnapKitの概要と基本的な実装に関して理解が進みましたら幸いです。

明日のレコチョク Advent Calendarは19日目【Swift】Widgetの作り方 〜iOS 16対応版〜です。お楽しみに!

参考資料

アバター画像

長島大和

目次