NSLayoutConstraintをプログラムでシンプルに書く

  • 19
    Like
  • 0
    Comment
More than 1 year has passed since last update.

役に立つかどうかよくわからないTipsシリーズ

storyboard や xib で AutoLayout として設定する時に
大事なのは「Constraint」
レイアウト制約です

objective-c 時代から苦手な人も多いようで
一番最初は自分も戸惑いが大きかったですが
慣れてみるとこんなに便利なものはありません

本来であれば、IB上ですべて済ませられれば理想的ですが
動的にビューを生成したりするときには
プログラマブルに書いていく必要があります

NSLayoutConstraintを生成するのは、2通りのやり方があります

ここでの例は
hogeViewというViewを、self.viewの上部にぴったり44pxの高さで配置する」
(ここでいうselfはViewControllerとする)
という表示を期待しています

1.引数たっぷりのコンストラクタで作る

self.view.addConstraints(
    [
        NSLayoutConstraint(
            item:       hogeView,
            attribute:  .Top,
            relatedBy:  .Equal,
            toItem:     self.view,
            attribute:  .Top,
            multiplier: 1.0,
            constant:   0
        ),
        NSLayoutConstraint(
            item:       hogeView,
            attribute:  .Leading,
            relatedBy:  .Equal,
            toItem:     self.view,
            attribute:  .Leading,
            multiplier: 1.0,
            constant:   0
        ),
        NSLayoutConstraint(
            item:       hogeView,
            attribute:  .Trailing,
            relatedBy:  .Equal,
            toItem:     self.view,
            attribute:  .Trailing,
            multiplier: 1.0,
            constant:   0
        ),
        NSLayoutConstraint(
            item:       hogeView,
            attribute:  .Height,
            relatedBy:  .Equal,
            toItem:     nil,
            attribute:  .Height,
            multiplier: 1.0,
            constant:   44.0
        ),
    ]
)

2.規定の文字列を渡すコンストラクタで作る

let views = ["hoge": hogeView]

self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-0-[hoge]-0-|",
    options: NSLayoutFormatOptions(),
    metrics: nil,
    views: views
    )
)
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
    "V:|-0-[hoge(44)]",
    options: NSLayoutFormatOptions(),
    metrics: nil,
    views: views
    )
)

まぁ、この辺は好みなのですが、後者はどうにも好きになれません

上の例では1つのビューが対象なのでシンプルですが、
複数のビューが複雑に絡みだすと
メンテナンスするときにわかりづらいし、なおしづらいからです

しかしながら、前者の引数たっぷりのコンストラクタは
ソースが冗長になりすぎます

なので、このような関数をトップレベルに作ると便利になります

/// 制約(NSLayoutConstraint)を生成する
/// - parameter item: 制約を追加するオブジェクト
/// - parameter attr: 制約を追加するオブジェクトに与える属性
/// - parameter to: 制約の相手
/// - parameter attrTo: 制約相手に使用する属性
/// - parameter constant: 定数値
/// - parameter multiplier: 乗数値
/// - parameter relate: 計算式の関係性
/// - parameter priority: 制約の優先度
/// - returns: 制約(NSLayoutConstraint)オブジェクト
public func Constraint(item: AnyObject, _ attr: NSLayoutAttribute, to: AnyObject?, _ attrTo: NSLayoutAttribute, constant: CGFloat = 0.0, multiplier: CGFloat = 1.0, relate: NSLayoutRelation = .Equal, priority: UILayoutPriority = UILayoutPriorityRequired) -> NSLayoutConstraint {
    let ret = NSLayoutConstraint(
        item:       item,
        attribute:  attr,
        relatedBy:  relate,
        toItem:     to,
        attribute:  attrTo,
        multiplier: multiplier,
        constant:   constant
    )
    ret.priority = priority
    return ret
}

「うわっ、なにこれ? 引数たっぷりじゃん」
ってなりましたか?

しかしながら、引数constant以降はすべて省略可能な引数です

IB上で AutoLayout を触る時を想定したときに
それほど変更することが少ないものは省略可能としておくことで、書くことを少なくしています
必要なものだけ引数に追加するイメージですね

さらに、属性をあらわすNSLayoutAttribute用の引数 attrattrToはメソッドラベルを省けるようにしています。
どうせ制約対象のitemもしくはtoの属性は必要なんだから・・・という考え方です

これにより、下記のようにソースを書くことができます

self.view.addConstraints([
    Constraint(hogeView, .Top,    to: self.view, .Top),
    Constraint(hogeView, .Bottom, to: self.view, .Bottom),
    Constraint(hogeView, .Left,   to: self.view, .Left),
    Constraint(hogeView, .Width,  to: nil,       .Width, constant: 44),
])

これで例と同じ表示をしてくれるようになります
シンプルになりましたね

IBでピタピタと制約を貼っていくのと、それほど変わらず直感的で労力少なめに出来る気がしませんか?