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

More than 3 years have 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でピタピタと制約を貼っていくのと、それほど変わらず直感的で労力少なめに出来る気がしませんか?