UIStackView「押しつぶされた」非表示ビューの「制約を同時に満たすことができない」


95

UIStackViewの「行」が押しつぶされると、AutoLayout警告がスローされます。ただし、これらは正常に表示され、以下の種類のロギング以外に問題はありません。

制約を同時に満たすことができません。おそらく、次のリストの少なくとも1つの制約は、望ましくないものです。これを試してください:(1)各制約を見て、予期しないものを見つけ出してください。(2)不要な制約を追加したコードを見つけて修正します。(注:NSAutoresizingMaskLayoutConstraints理解できない場合は、UIViewプロパティのドキュメントを参照してくださいtranslatesAutoresizingMaskIntoConstraints)(

したがって、これを修正する方法はまだわかりませんが、それだけで迷惑であることにほかならないようです。

誰かがそれを解決する方法を知っていますか?興味深いことに、レイアウト制約には「UISV-hiding」というタグが付けられていることがよくあります。これは、サブインスタンスまたはこのインスタンスの何かの高さの最小値を無視する必要があることを示していますか?


1
これはiOS11で修正されたようであり、ここでは警告は表示されません
トラッパー

回答:


204

この問題が発生するのは、サブビューを内側UIStackViewから非表示に設定すると、アニメーション化するために、まず高さが0に制限されるためです。

次のエラーが発生しました:

2015-10-01 11:45:13.732 <redacted>[64455:6368084] 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. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

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.

私がやろうとしていたことは、各エッジに8ポイントずつインセットを含むUIView内にを配置するUIStackViewことUISegmentedControlでした。

非表示に設定すると、コンテナビューが高さ0に制限されますが、上から下に一連の制限があるため、競合が発生しました。

この問題を解決するために、UISV-hiding必要に応じて制約が優先されるように、8ptの上下制約の優先順位を1000から999に変更しました。


これらに高さまたは幅の制約があるだけで、それらの優先順位を下げると、機能しないように見えることに注意してください。高さ/幅を削除し、上部の先行トレーリングボトムを追加する必要があります。次に、優先度を低く設定すると機能します
bolnad

4
優先順位の変更も私にとってはうまくいきました。また、未使用のサイズクラスから誤ってコピーされた余分な(淡色表示された)制約を削除します。重要なヒント:これらの問題をより簡単にデバッグするには、各制約にIDENTIFER文字列を設定します。次に、どのコンストレイントがいたずらであるかをデバッグメッセージで確認できます。
Womble

3
私の場合、高さの優先度を下げるだけで機能します。
pixelfreak 2016

そのIDENTIFIERのヒントは素晴らしいです!デバッグメッセージの名前に制約を付ける方法を常に考えていました。制約自体ではなく、ビューに何かを追加しようと常に考えていました。ありがとう@Womble!
ライアン

ありがとう!Xcode:バージョン8.3.3(8E3004b)
Michael

51

私は解決するのが容易ではない同様の問題を抱えていました。私の場合、スタックビューにスタックビューが埋め込まれていました。内部UIStackViewには2つのラベルがあり、ゼロ以外の間隔が指定されていました。

addArrangedSubview()を呼び出すと、次のような制約が自動的に作成されます。

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

これで、innerStackViewを非表示にしようとすると、あいまいな制約の警告が表示されます。

理由を理解するために、まずがに等しいときにこれが起こらない理由を見てみましょう。を呼び出すと、@ liamnicholsは正しかったです... は魔法でこの呼び出しをインターセプトし、高さUISVを非表示にする優先度1000の制約を作成します(必須)。おそらくこれは、ブロック内で非表示のコードが呼び出された場合にスタックビューの要素をビューの外でアニメーション化できるようにするためです。残念ながら、この制約が追加されるのを防ぐ方法はないようです。それにもかかわらず、次のことが発生するため、「制約を同時に満たすことができません」(USSC)警告は表示されません。innerStackView.spacing0innerStackView.hidden = trueouterStackView0UIView.animationWithDuration()

  1. label1の高さは0に設定されています
  2. 2つのラベル間の間隔はすでに0として定義されています
  3. label2の高さは0に設定されています
  4. innerStackViewの高さが0に設定されている

これらの4つの制約が満たされることは明らかです。スタックビューは、すべてを高さ0のピクセルに単純化します。

バグのある例に戻って、をに設定するspacing2、次の制約が適用されます。

  1. label1の高さは0に設定されています
  2. 2つのラベルの間隔は、優先順位1000で2ピクセルの高さとしてスタックビューによって自動的に作成されました。
  3. label2の高さは0に設定されています
  4. innerStackViewの高さが0に設定されている

スタックビューの高さを0ピクセルにすることはできません。また、コンテンツの高さを2ピクセルにすることもできません。制約を満たすことができません。

注:この動作は、簡単な例で確認できます。UIViewを配置されたサブビューとしてスタックビューに追加するだけです。次に、優先順位1000でそのUIViewに高さ制約を設定します。それで、hideを呼び出してみてください。

注:何らかの理由で、これは私のスタックビューがUICollectionViewCellまたはUITableViewCellのサブビューである場合にのみ発生しました。ただし、innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)内部スタックビューを非表示にした後、次の実行ループを呼び出すことで、セルの外でこの動作を再現できます。

注:UIView.performWithoutAnimationsでコードを実行しようとしても、スタックビューは高さ0の制約を追加するため、USSC警告が発生します。


この問題には少なくとも3つの解決策があります。

  1. スタックビューで要素を非表示にする前に、それがスタックビューであるかどうかを確認し、そうである場合spacing0 に変更します。コンテンツを再度表示するときは常に、プロセスを元に戻す必要があり(元の間隔を覚えておく必要があるため)、これは面倒です。
  2. スタックビューで要素を非表示にする代わりに、を呼び出しますremoveFromSuperviewこれは、プロセスを逆にするときに、削除したアイテムを挿入する場所を覚えておく必要があるため、さらに厄介です。removeArrangedSubviewを呼び出して非表示にするだけで最適化できますが、実行する必要のある簿記はたくさんあります。
  3. ネストされたスタックビュー(ゼロ以外spacing)をUIViewでラップします。少なくとも1つの制約を必須ではない優先順位(999以下)として指定します。簿記を行う必要がないため、これが最良のソリューションです。私の例では、スタックビューとラッパービューの間に1000でトップ、リーディング、トレーリングコンストレイントを作成し、スタックビューの下部からラッパービューに999コンストレイントを作成しました。このようにして、外部スタックビューが高さ0の制約を作成すると、999の制約が破られ、USSC警告が表示されなくなります。(注:これは、UICollectionViewCellサブクラスのcontentView.translatesAutoResizingMaskToConstraintsをに設定する必要がある場合のfalse解決策に似ています)

要約すると、この動作が発生する理由は次のとおりです。

  1. スタックビューにマネージサブビューを追加すると、Appleは1000の優先順位制約を自動的に作成します。
  2. Appleは、スタックビューのサブビューを非表示にすると、高さ0の制約を自動的に作成します。

Appleが(1)制約の優先順位(特にスペーサー)の指定を許可した場合、または(2)自動UISV非表示制約のオプトアウトを許可した場合、この問題は簡単に解決されます。


6
超徹底的で有益な説明をありがとう。ただし、これは間違いなく、スタックビューに関するApple側のバグのようです。基本的に、「非表示」機能は「間隔」機能と互換性がありません。それ以来、彼らがこれを解決した、または余分なビューを含むハッキングを防ぐためにいくつかの機能を追加したというアイデアはありますか?(ここでも、潜在的なソリューションの大きな内訳と#3の優雅さに同意します)
マルキー

1
ニーズのすべてのUIStackView子は、非表示/再表示しようとしているものにUIStackViewラップされる必要がありUIViewますか?
エイドリアン

1
これはAppleの見落としのように思えます。特に、の使用に関する警告とエラーはUIStackView不可解で理解しにくい傾向があるためです。
bompf

これは命の恩人です。セルが再利用されるたびに、UITableViewCellに追加されたUIStackViewsがAutoLayoutエラーログのスパムを引き起こすという問題がありました。下部アンカー制約の優先度を低く設定してUIViewにstackViewを埋め込むと、問題が解決しました。これにより、ビューデバッガーは、stackViewの要素を不明確な高さとして表示しますが、エラーログスパムなしでアプリに正しく表示されます。ありがとうございました。
Womble

6

ほとんどの場合、このエラーは、競合を解消するために制約の優先度を下げることで解決できます。


どういう意味ですか?制約は....すべてに積層されたビュー内の相対的である
ベンギルド

すみません、あなたの質問を正しく理解できません。私の英語はそうですが、ビューが非表示になると、制約はビューの魔女に関連していると思います。それがあなたの疑問であるかどうかはわかりませんが、私がお手伝いできるといいのですが
Luciano Almeida、

これは、コンテンツが押しつぶされたとき、または表示/非表示になっているときに発生しているようです。この場合、部分的に表示されます。—たぶん、高さ0に押しつぶすことができるので、最小の垂直定数を実際に調べて排除する必要があるのでしょうか。
ベンギルド2015

2

ビューを非表示に設定すると、 UIStackviewはそれを離れてアニメーション化しようとします。その効果が必要な場合は、制約が競合しないように適切な優先順位を設定する必要があります(多くの人が上で提案したように)。

ただし、アニメーションを気にしない場合(おそらく、ViewDidLoadでそれを非表示にしている場合)removeFromSuperviewは、同じ効果をもたらす単純なものにすることができますが、ビューと一緒に削除されるため、制約の問題はありません。


1

@Sensefulの回答に基づいて、スタックビューをビューにラップし、彼または彼女が推奨する制約を適用するUIStackView拡張機能を次に示します。

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

を追加する代わりにstackView、を使用しますstackView.wrapped()


1

まず、他の人が提案したように、制御できる制約、つまりUIStackViewに固有の制約が優先度999に設定されていないことを確認してください。これにより、ビューが非表示のときにオーバーライドできます。

それでも問題が発生する場合は、非表示のStackViewの間隔が原因である可能性があります。 私の解決策は、UIViewをスペーサーとして追加し、UIStackViewの間隔をゼロに設定することでした。 次に、View.heightまたはView.width制約(垂直または水平スタックに応じて)をStackViewの間隔に設定します。

次に、新しく追加したビューのコンテンツハグとコンテンツ圧縮耐性の優先順位を調整します。親StackViewの分布も変更する必要がある場合があります。

上記はすべてInterface Builderで実行できます。さらに、不要な間隔を空けないように、新しく追加されたビューの一部をプログラムで非表示/再表示する必要がある場合があります。


1

最近、を非表示にすると、自動レイアウトエラーが発生しましたUIStackView。一連の本を保管してスタックをラッピングするのではなく、UIViews自分parentStackView用のアウトレットと、非表示/再表示したい子供用のアウトレットを作成することを選択しました。

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

ストーリーボードでは、parentStackは次のようになります。

ここに画像の説明を入力してください

4つの子があり、それぞれの子の中にスタックビューの束があります。スタックビューを非表示にすると、スタックビューであるUI要素もある場合、自動レイアウトエラーのストリームが表示されます。非表示にするのではなく、それらを削除することを選択しました。

私の例でparentStackViewsは、4つの要素の配列が含まれています:Top Stack View、StackViewNumber1、Stack View Number 2、およびStop Button。のインデックスはarrangedSubviewsそれぞれ0、1、2、3です。非表示にする場合は、parentStackView's arrangedSubviews配列から削除するだけです。弱くないわけではないので、メモリに残り、後で目的のインデックスに戻すことができます。私はそれを再初期化していないので、必要になるまでハングアウトしますが、メモリを膨らませません。

だから基本的には...

1)親スタックと非表示/再表示したい子のIBOutletsをストーリーボードにドラッグします。

2)それらを非表示にする場合は、非表示にするスタックをparentStackView's arrangedSubviews配列から削除します。

3)で電話self.view.layoutIfNeeded()UIView.animateWithDurationます。

最後の2つのstackViewはそうではないことに注意してくださいweak。それらを再表示するときのためにそれらを保持する必要があります。

stackViewNumber2を非表示にしたいとしましょう:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

次に、アニメーション化します。

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

stackViewNumber2後で「再表示」する場合は、目的のparentStackView arrangedSubViewsインデックスに挿入して、更新をアニメーション化できます。

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

制約の簿記や優先順位の変更などよりもはるかに簡単であることがわかりました。

デフォルトで非表示にするものがある場合は、それをストーリーボードにレイアウトして削除しviewDidLoad、アニメーションを使用せずにを使用して更新しview.layoutIfNeeded()ます。


1

組み込みのスタックビューでも同じエラーが発生しましたが、実行時にはすべて正常に機能しました。

isHidden = true親スタックビューを非表示にする前に、すべてのサブスタックビューを最初に非表示にする(設定)ことで、制約エラーを解決しました。

これを行うことで、下位に配置されたビューを削除する複雑さがすべてなくなり、それらを再び追加する必要がある場合のインデックスを維持できます。

お役に立てれば。


1

Sensefulは、上記の問題の根本に対する優れた回答を提供してくれたので、直接解決策に進みます。

必要なのは、すべてのstackView制約の優先度を1000よりも低く設定することだけです(999で処理できます)。たとえば、stackViewがスーパービューに対して左、右、上、下に制約されている場合、4つの制約すべての優先度は1000未満である必要があります。


0

特定のサイズクラス(例:wCompact hRegular)で作業中に制約を作成し、別のサイズクラス(例:wAny hAny)に切り替えたときに複製を作成した可能性があります。さまざまなサイズのクラスのUIオブジェクトの制約を確認し、制約に異常がないか確認してください。競合する制約を示す赤い線が表示されます。申し訳ありませんが10個の評判ポイントを取得するまで、画像を配置できません:/


ああ。しかし、drive.google.com / file / d / 0B
FM

ええ、インターフェイスビルダーでサイズクラスを切り替えても、赤はまったく表示されません。「Any」サイズのみを使用しました。
ベン・ギルド

0

一度にUIStackView全体を非表示にしたかったのですが、OPと同じエラーが発生していました。これで修正されました。

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}

必要な制約(priority = 1000)を不要(priority <= 999)に変更すると、自動レイアウトエンジンがエラーを表示するため、これは私にはうまくいきませんでした。
センスフル2016年

0

高さの制約があるボタンの行がありました。これは、1つのボタンが非表示になっているときに発生します。そのボタンの高さ制約の優先度を999に設定すると、問題が解決しました。


-2

このエラーは、UIStackViewとは関係ありません。同じ優先順位の競合制約がある場合に発生します。たとえば、ビューの幅が100であるという制約があり、同時に別の制約がある場合は、ビューの幅がそのコンテナの25%であることが示されます。明らかに、2つの矛盾する制約があります。解決策はそれらを削除することです。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.