[Swift][OSS] iOS 9以上でAuto Layoutの制約が簡潔に書けるTinyConstraintsを試してみた
はじめに
最近、離乳食でハチミツを食べていた男の子が乳児ボツリヌス症で亡くなったニュースに心を痛めている加藤です。悲しすぎますね...
さて、今回は記事執筆時点でStarの数が1400を超えているTinyConstraintsというSwiftのライブラリが良さげだったので試してみました。
検証環境
本記事は以下の環境で検証を行っています。
- macOS Sierra バージョン 10.12.4
- Xcode Version 8.3 (8E162)
- iPhone 7シミュレータ iOS 10.3(14E269)
- Swift 3.1
- CocoaPods バージョン 1.2.0
TinyConstraintsとは
TinyConstraints is the syntactic sugar that makes Auto Layout sweeter for human use.
README.mdより引用
AutoLayoutが使いやすくなるシンタックスシュガーとのことです。 MITライセンスで公開されています。
ソースコードを見たところ、NSLayoutAnchorを使った制約のラッパーという感じでした。
NSLayoutAnchorはiOS 9以上でないと使えないのでご注意ください。
podspecでも「ios.deployment_target」が9.0
になっています。
NSLayoutAnchorについては以前紹介記事を書きましたので宜ければご一読ください。
サンプルアプリ
以下はExampleをシミュレーターで動かした結果です。
こんなのが簡単にできるって素敵ですね。
導入
早速導入してみます。CocoaPodsとCarthageに対応していますが、今回はCocoaPodsでインストールします。
pod init
でPodfileを作成し、以下のようにpod "TinyConstraints"
を記載します。
後はpod install
すればOKです。
target ターゲット名 do use_frameworks! pod "TinyConstraints" end
記事執筆時点では最新バージョン2.0.0
がインストールされました。
使ってみる
pod installできたので使ってみます。
TinyConstraintsを使うにはファイルに以下のimport文が必要です。
import TinyConstraints
SubViewをSuperViewと同じサイズにする
SubViewをSuperViewと同じサイズにする場合、いくつか方法はありますがNSLayoutAnchorを使う場合は以下のような書き方になると思います。
let subView = UIView() subView.translatesAutoresizingMaskIntoConstraints = false subView.backgroundColor = .green view.addSubview(subView) // TinyConstraintsを使わない場合は通常こんな感じで書く NSLayoutConstraint.activate([ subView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0), subView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0), subView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0), subView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0) ])
上記のハイライト部分の制約をTinyConstraintsを使って書くと以下のようになります。
// TinyConstraintsを使った場合の書き方 subView.edges(to: view)
1行で書けてしまいます!素敵!!
また、上下左右に余白をつけることも可能です。 以下は上下左右に16ptの余白をつける場合の例です。
// 上下左右に16ptずつ余白を設ける場合 subView.edges(to: view, insets: EdgeInsets(top: 16, left: 16, bottom: -16, right: -16))
SubViewの中心をSuperViewと同じにする
Viewのサイズは決まっているので中心だけSuperViewと同じにしたい場合もありますよね。 通常、centerXAnchorやcenterYAnchorを使って書く場合は以下のようになります。
let subView = UIView() subView.translatesAutoresizingMaskIntoConstraints = false subView.backgroundColor = .green view.addSubview(subView) // SubViewの幅と高さはSuperViewの2分の1 NSLayoutConstraint.activate([ subView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5), subView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5) ]) // SubViewの中心とSuperViewの中心を合わせる(TinyConstraintsを使わない場合) NSLayoutConstraint.activate([ subView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0), subView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0) ])
上記のハイライト部分の制約をTinyConstraintsを使って書くと以下のようになります。
// SubViewの中心とSuperViewの中心を合わせる(TinyConstraintsを使った場合) subView.center(in: view)
これも1行で書けてしまいました。
また、offset
を指定すれば中心からずらすことも可能です。
// 縦横に50ptずつずらす subView.center(in: view, offset: CGPoint(x: 50, y: 50))
幅や高さの制約
width
メソッド、height
メソッドを使うことで幅や高さの制約を付けられます。
// 幅と高さを100pt固定にする subView.width(100) subView.height(100)
CGSize
を指定しても幅と高さの制約をつけられます。
// 幅と高さを100pt固定にする subView.size(CGSize(width: 100, height: 100))
〜以上、〜以下の制約もつけられます。
// 幅を10pt以上、50pt以下にする subView.width(min: 10, max: 50)
また、relationやpriority、isActiveを指定することも可能です。
// 幅が100pt以上の制約 subView.width(100, relation: .equalOrGreater, priority: .required, isActive: true)
isActiveをfalseにし変数に保持することで、制約生成時には制約を適用せず、任意のタイミングで制約を有効にすることが可能です。
// 制約生成時は制約を有効にしない let widthConstrant = subView.width(100, relation: .equalOrGreater, priority: .required, isActive: false) // 任意のタイミングで制約を有効にする widthConstrant.isActive = true
制約を保持しておくことで、後から制約の値を変更することが可能です。 以下は、初期状態として幅と高さを0ptにする制約を生成・保持しておき、 ボタンをタップしたら保持しておいた制約の幅と高さを200ptに変更する例です。 iOS 10から使えるUIViewPropertyAnimatorを使ってアニメーションさせてみました。
// 制約を保持しておく変数 var widthConstraint: Constraint! var heightConstraint: Constraint! override func viewDidLoad() { super.viewDidLoad() let subView = UIView() subView.translatesAutoresizingMaskIntoConstraints = false subView.backgroundColor = .green view.addSubview(subView) // 初期状態は幅と高さを0ptにしておく widthConstraint = subView.width(0) heightConstraint = subView.height(0) // SubViewの中心とSuperViewの中心を合わせる subView.center(in: view) } @IBAction func didTapStartAnimationButton(_ sender: Any) { // 幅と高さを200ptにアニメーション付きで変更 widthConstraint.constant = 200 heightConstraint.constant = 200 UIViewPropertyAnimator(duration: 1.0, dampingRatio: 0.4) { self.view.layoutIfNeeded() }.startAnimation() }
Stackで縦方向 or 横方向にViewを並べる
stackメソッドを使えば、縦方向 もしくは 横方向にViewを並べることができます。 以下は3つのViewを垂直方向に並べる例です。
let subView1 = UIView() subView1.translatesAutoresizingMaskIntoConstraints = false subView1.backgroundColor = .green let subView2 = UIView() subView2.translatesAutoresizingMaskIntoConstraints = false subView2.backgroundColor = .blue let subView3 = UIView() subView3.translatesAutoresizingMaskIntoConstraints = false subView3.backgroundColor = .yellow let views = [subView1, subView2, subView3] view.stack(views, axis: .vertical, width: 100, height: 100, spacing: 10)
垂直方向に並べた場合、指定したwidthは無視され、SuperViewと同じ幅になります。 heightを100ptに指定していますが、配列で指定したViewの内、最後のViewの高さは指定したheightが無視されてbottomAnchor(下辺)がSuperViewと一致する高さになります。 逆に水平方向に並べた場合は指定したheightが無視され、SuperViewと同じ高さになります。 また、配列で指定したViewの内、最後のViewの幅は指定したwidthが無視されてrightAnchor(右辺)がSuperViewと一致する幅になります。
おわりに
iOS 9以上でAuto Layoutの制約が簡潔に書けるTinyConstraintsを試してみました。
iOS 9以上で使えるNSLayoutAnchorを使うと制約を簡潔に書けますが、TinyConstraintsを使うと更に短く書けます。
使ってみてはいかがでしょうか。