パッと見、訳の分からない文字列のせいで取っ付きにくい印象がありますが慣れてしまえば実はそうでもないんです。制約付けのコツは、ストーリーボードでの制約にも言えることですが自分が部品になったつもりになって「自分の居場所は何と何が決まれば確定するのだろうか...」と考えることかなと思います。
お約束
-
translatesAutoresizingMaskIntoConstraints = false
を忘れずに! - 制約を付けるのは
addSubviewの後
です!
実践/サンプル
1.縦横サイズの指定
横サイズの指定
H:[{object}(=={value})]
縦サイズの指定
V:[{object}(=={value})]
※{object}はオブジェクトのディクショナリキーを指定します。以下同様
試してみましょう。次のコードはボタンをひとつ作成し、縦横サイズの指定をしています。横を示す「H:」は省略可能です。制約はaddConstraintsで設定します。メソッド名は複数形です。viewsパラメタにはオブジェクトのディクショナリを渡します。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ボタン
let button = UIButton(type: UIButtonType.system)
button.backgroundColor = UIColor.yellow
button.setTitle("button", for: .normal)
view.addSubview(button)
// オブジェクトをディクショナリに格納
let objects = ["button":button]
objects.forEach { $1.translatesAutoresizingMaskIntoConstraints = false }
button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==140)]", options: .alignAllTop, metrics: nil, views: objects))
button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
}
}
2.オフセットで位置を指定
左マージンを指定
H:|-{value}-[{object}]
上マージンを指定
V:|-{value}-[{object}]
右マージンを指定
H:[{object}]-{value}-|
下マージンを指定
V:[{object}]-{value}-|
左右マージンを指定
H:|-{value}-[{object}]-{value}-|
上下マージンを指定
V:|-{value}-[{object}]-{value}-|
「|」が隣接するオブジェクトの境界線、「-」が距離を示しているわけですね。
コードに以下を加えてみます。ボタンが外側(=View)から横50px、上から100pxの位置に表示されます。addConstraintsメソッドをコールしているのが相対関係にある外側のオブジェクト(view)であることに注意してください。
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]", options: .alignAllTop, metrics: nil, views: objects))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-100-[button]", options: .alignAllTop, metrics: nil, views: objects))
ちなみに左右のマージンを50pxにするには H:|-50-[button] を H:|-50-[button]-50-| にします。
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]-50-|", options: .alignAllTop, metrics: nil, views: objects))
3.オブジェクト間での制約
オブジェクト2の横サイズをオブジェクト1の横サイズと同じにする
H:[{object1}(=={object2})]
オブジェクト2の縦サイズをオブジェクト1の縦サイズと同じにする
V:[{object1}(=={object2})]
オブジェクト2をオブジェクト1の右横に配置する
H:[{object1}]-{value}-[{object2}]
オブジェクト2をオブジェクト1の下に配置する
V:[{object1}]-{value}-[{object2}]
テキストフィールドをひとつ追加して、ボタンと同じサイズ、ボタンの横にマージン10pxを指定して表示させてみます。コードにテキストフィールドの生成処理を追加し、オブジェクト配列に追加します。
// テキストフィールド
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.text = "textfield"
view.addSubview(textField)
// オブジェクトをディクショナリに格納
let objects = ["button":button,"textField":textField]
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button]-10-[textField]", options: .alignAllBottom, metrics: nil, views: objects))
現在のコードから H:[button]-10-[textField] を V:[button]-10-[textField] に変更するだけでテキストフィールドがボタンの下に表示されそうですが実はそれだけではダメです。上下の制約に変更した場合、optionsのパラメタ(揃えの基準)も適したものを設定しないといけません。「.alignAllLeft」か「.alignAllRight」にする必要があります。
4.各要素を一直線に並べたレイアウト
オブジェクト1とのマージンを確保しながらオブジェクト2の横サイズを可変にする
|-[{object1}]-[{object2}(>={value})]-|
ボタンの横マージンとテキストフィールドの横幅と位置の制約をコメントアウトして、横並びにする制約を加えます。数値指定のない「-」のみの場合は8ピクセルのマージンが付きます。この例ではテキストフィールドが20px以下にならない範囲で可変となります。
//view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]", options: .alignAllTop, metrics: nil, views: objects))
//view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
//view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button]-10-[textField]", options: .alignAllBottom, metrics: nil, views: objects))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|-[button]-[textField(>=20)]-|", options: .alignAllBottom, metrics: nil, views: objects))
次のコードを加えてボタンの横幅を変えてみます。
button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
テキストフィールドの横幅も追従します。
テストコード全体
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// ボタン
let button = UIButton(type: UIButtonType.system)
button.backgroundColor = UIColor.yellow
button.setTitle("button", for: .normal)
view.addSubview(button)
// テキストフィールド
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.text = "textfield"
view.addSubview(textField)
// オブジェクトをディクショナリに格納
let objects = ["button":button,"textField":textField]
objects.forEach { $1.translatesAutoresizingMaskIntoConstraints = false }
// オブジェクト配列
let objects = ["button":button,"textField":textField]
objects.forEach { $1.translatesAutoresizingMaskIntoConstraints = false }
button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==140)]", options: .alignAllTop, metrics: nil, views: objects))
button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
//view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]", options: .alignAllTop, metrics: nil, views: objects))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-100-[button]", options: .alignAllTop, metrics: nil, views: objects))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
//view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
//view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button]-10-[textField]", options: .alignAllBottom, metrics: nil, views: objects))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|-[button]-10-[textField(>=20)]-|", options: .alignAllBottom, metrics: nil, views: objects))
button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
}
}