Xib(storyboard)からAutoLayoutを設定した上で、コードから制約を追加しようとするとこのような警告が出力されて、上手く動いたり、動かなかったりすることがあります。
この問題について考えます。
2016-02-01 01:14:22.181 TestAutoLayout[3159:1247734] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x7ffc61c43270 H:|-(100)-[UIButton:0x7ffc61c33da0'Button'] (Names: '|':UIView:0x7ffc61c355c0 )>",
"<NSLayoutConstraint:0x7ffc61d90740 H:|-(200)-[UIButton:0x7ffc61c33da0'Button'] (Names: '|':UIView:0x7ffc61c355c0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7ffc61d90740 H:|-(200)-[UIButton:0x7ffc61c33da0'Button'] (Names: '|':UIView:0x7ffc61c355c0 )>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
概要
例えば「ボタンを押したらマージンを広げて他のコントロールを配置したい」とかありますよね。
具体的に言うと、interface builder(storyboard)でデフォルトのデザインをしておき、ボタンを押したら、マージンを調整したい、場合などです。
何も考えずに上記を行うと、掲題にある警告が出たり、上手く動かなかったりします。
例
初期状態。ボタンが上下左右マージン0でおかれています。
interface builderで見たままです。
タップすると左右のマージンが広がる。
もう一回押すとマージン0に戻ります。
何を警告されているのか?
NSAutoLayoutのインタフェースは追加、削除しかないため(更新のインタフェースがあったら教えて下さい!)、ボタンをおした時の制約を追加しようとすると、デフォルトの制約とぶつかり、どっちが正しいかわからない、という警告です(ログにある通りです)。
どうすればいいのか?
案0 IBOutletでInterfaceBuilderと結びつけておきconstantを変更する(圧倒的感謝 to @woxtu)
こうして…
@IBOutlet weak var constraintMargin: NSLayoutConstraint!
ここから結びつけて
こうじゃ。
self.constraintMargin.constant = 40
案1 コードのみで設定する
Xcodeから衝突する制約を削除します。
今回は左右マージン設定を削除しました。
Interface Builder上では制約が不十分だ!と警告が出ますが、「うるせえ!」と強い意志を持って無視します。
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
private var on: Bool = false
var contraintsOn: [NSLayoutConstraint]! // 強気の!
var contraintsOff: [NSLayoutConstraint]!
override func viewDidLoad() {
super.viewDidLoad()
loadContraints()
applyConstraint()
}
private func loadContraints() {
let viewDictionary:Dictionary = ["button": button]
contraintsOn = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-\(100)-[button]-\(100)-|",
options : NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: viewDictionary)
contraintsOff = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-\(0)-[button]-\(0)-|",
options : NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: viewDictionary)
}
@IBAction func buttonAction() {
applyConstraint()
}
private func applyConstraint() {
on = !on
// clear constraints. 三項演算子を使ってもいいけどこっちのが見やすいと僕は思いました。
NSLayoutConstraint.deactivateConstraints(contraintsOff)
NSLayoutConstraint.deactivateConstraints(contraintsOn)
// apply
NSLayoutConstraint.activateConstraints(on ? contraintsOn! : contraintsOff!)
}
}
viewDidLoad
の後に on/off に対する制約を作っておき、クリアしてから、制約を追加する案。
冗長な気がしますが、無しではないのかなぁ…。
案2 UIStackViewでできないか考える
iOS9からUIStackViewというのが使えます。
今回のようにマージンを動的に変更したのは、別のコントロールを表示したり、隠したり、したいからというケースが多いと思います。
その場合、UIStackViewがいい感じかもしれません。
岸川さんのスライドがすごく良いです。
表示、非表示をコードから切り替えるだけで、viewを調整してくれます。
iOS9で縛るのは2016年現在はまだまだ厳しいかと思います。
スライドにもありますが、iOS7以降にportした OAStackView というのもあります。
Interface BuilderからもCustom Class欄に記載すれば、設定できるようになっているし、できが良いです。
ただ、Interface Builder上で綺麗なプレビューは諦めて、部品を配置するだけ、と考えたほうが良い気がします(プレビューを綺麗にしようとしてAutoLayoutを追加すると振り出しに戻ります)。
まとめ
リリースが近い!とにかくサクッと直したいなら案1。
綺麗にやりたいなら案2でしょうか。
他に解決方法があれば、教えていただきたく。