UIStackView "Einschränkungen können nicht gleichzeitig erfüllt werden" für "gequetschte" versteckte Ansichten

95

Wenn meine "Zeilen" von UIStackView gequetscht werden, werden AutoLayoutWarnungen ausgegeben . Sie werden jedoch gut angezeigt und nichts anderes ist falsch als diese Art von Protokollierung:

Einschränkungen können nicht gleichzeitig erfüllt werden. Wahrscheinlich ist mindestens eine der Einschränkungen in der folgenden Liste eine, die Sie nicht möchten. Versuchen Sie Folgendes: (1) Sehen Sie sich jede Einschränkung an und versuchen Sie herauszufinden, welche Sie nicht erwarten. (2) Finden Sie den Code, der die unerwünschte Einschränkung oder die unerwünschten Einschränkungen hinzugefügt hat, und beheben Sie ihn. (Hinweis: Wenn Sie feststellen, NSAutoresizingMaskLayoutConstraintsdass Sie nicht verstehen, lesen Sie die Dokumentation zur UIViewEigenschaft. translatesAutoresizingMaskIntoConstraints) (

Ich bin mir noch nicht sicher, wie ich das beheben soll, aber es scheint nichts zu zerstören, außer nur nervig zu sein.

Weiß jemand, wie man es löst? Interessanterweise werden die Layouteinschränkungen häufig mit "UISV-Ausblenden" gekennzeichnet , was darauf hinweist, dass möglicherweise die Mindesthöhen für Unteransichten oder etwas in diesem Fall ignoriert werden sollten.

Ben Guild
quelle
1
Dies scheint in iOS11 behoben zu sein, hier werden keine Warnungen angezeigt
Trapper

Antworten:

204

Dieses Problem tritt auf, weil beim Festlegen einer Unteransicht von innen UIStackViewnach ausgeblendet zunächst die Höhe auf Null beschränkt wird, um sie zu animieren.

Ich habe den folgenden Fehler erhalten:

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.

Was ich versuchte zu tun, war ein UIViewin mein zu platzieren UIStackView, das einen UISegmentedControlEinschub von 8 Punkten an jeder Kante enthielt .

Wenn ich es auf versteckt setzte, versuchte es, die Containeransicht auf eine Höhe von Null zu beschränken, aber da ich eine Reihe von Einschränkungen von oben nach unten habe, gab es einen Konflikt.

Um das Problem zu beheben, habe ich meine Priorität für 8pt oben und unten von 1000 auf 999 geändert, damit die UISV-hidingEinschränkung bei Bedarf Priorität haben kann.

Liamnichole
quelle
Es sollte beachtet werden, dass es nicht funktioniert, wenn Sie nur eine Höhen- oder Breitenbeschränkung für diese haben und deren Priorität verringern. Sie müssen die Höhe / Breite entfernen und die oberen führenden hinteren Böden hinzufügen, dann ihre Priorität als niedriger
festlegen
4
Das Ändern der Prioritäten hat auch bei mir funktioniert. Entfernen Sie außerdem alle überschüssigen (abgeblendeten) Einschränkungen, die versehentlich aus nicht verwendeten Größenklassen kopiert wurden. WICHTIGER TIPP: Um diese Probleme einfacher zu beheben, legen Sie für jede Einschränkung eine IDENTIFER-Zeichenfolge fest. Dann können Sie in der Debug-Nachricht sehen, welche Einschränkung ungezogen war.
Womble
3
In meinem Fall muss ich nur die Priorität auf die Höhe senken und es funktioniert.
Pixelfreak
Dieser IDENTIFIER-Tipp ist großartig! Ich habe mich immer gefragt, wie ich die Einschränkungen in den Debug-Nachrichtennamen angeben soll. Ich habe immer versucht, der Ansicht etwas hinzuzufügen, anstatt der Einschränkung selbst. Danke @Womble!
Ryan
Vielen Dank! Priorität von 1000 bis 999 hat den Trick gemacht.Xcode: Version 8.3.3 (8E3004b)
Michael Garito
51

Ich hatte ein ähnliches Problem, das nicht einfach zu lösen war. In meinem Fall war eine Stapelansicht in eine Stapelansicht eingebettet. In der internen UIStackView wurden zwei Beschriftungen und ein Abstand ungleich Null angegeben.

Wenn Sie addArrangedSubview () aufrufen, werden automatisch Einschränkungen erstellt, die den folgenden ähneln:

V:|[innerStackView]|              | = outerStackView

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

Wenn Sie nun versuchen, die innerStackView auszublenden, wird eine mehrdeutige Einschränkungswarnung angezeigt.

Um zu verstehen, warum, lassen Sie uns zuerst sehen, warum dies nicht passiert, wenn innerStackView.spacinges gleich ist 0. Wenn Sie anrufen innerStackView.hidden = true, @liamnichols richtig war ... das outerStackViewwird diesen Aufruf auf magische Weise abfangen, und schafft 0Höhe UISV-Versteck constrain mit Priorität 1000 (erforderlich). Vermutlich soll dies ermöglichen, dass Elemente in der Stapelansicht außerhalb der Ansicht animiert werden, falls Ihr Versteckcode innerhalb eines UIView.animationWithDuration()Blocks aufgerufen wird . Leider scheint es keine Möglichkeit zu geben, das Hinzufügen dieser Einschränkung zu verhindern. Sie erhalten jedoch keine Warnung "USSC)" Nicht gleichzeitig erfüllt werden können ", da Folgendes passiert:

  1. Die Höhe von label1 wird auf 0 gesetzt
  2. Der Abstand zwischen den beiden Beschriftungen wurde bereits als 0 definiert
  3. Die Höhe von label2 ist auf 0 gesetzt
  4. Die Höhe von innerStackView wird auf 0 gesetzt

Es ist klar zu sehen, dass diese 4 Bedingungen erfüllt werden können. Die Stapelansicht glättet einfach alles in ein Pixel mit einer Höhe von 0.

Zurück zum Buggy-Beispiel: Wenn wir den Wert spacingauf setzen 2, gelten folgende Einschränkungen:

  1. Die Höhe von label1 wird auf 0 gesetzt
  2. Der Abstand zwischen den beiden Beschriftungen wurde von der Stapelansicht automatisch als 2 Pixel hoch mit 1000 Prioritäten erstellt.
  3. Die Höhe von label2 ist auf 0 gesetzt
  4. Die Höhe von innerStackView wird auf 0 gesetzt

Die Stapelansicht kann nicht beide 0 Pixel hoch sein und ihr Inhalt darf 2 Pixel hoch sein. Die Einschränkungen können nicht erfüllt werden.

Hinweis: Sie können dieses Verhalten anhand eines einfacheren Beispiels sehen. Fügen Sie einer Stapelansicht einfach eine UIView als angeordnete Unteransicht hinzu. Legen Sie dann eine Höhenbeschränkung für dieses UIView mit der Priorität 1000 fest. Versuchen Sie nun, hide aufzurufen.

Hinweis: Aus irgendeinem Grund geschah dies nur, wenn meine Stapelansicht eine Unteransicht einer UICollectionViewCell oder UITableViewCell war. Sie können dieses Verhalten jedoch auch außerhalb einer Zelle reproduzieren, indem Sie innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)die nächste Ausführungsschleife aufrufen, nachdem Sie die innere Stapelansicht ausgeblendet haben.

Hinweis: Auch wenn Sie versuchen, den Code in einer UIView.performWithoutAnimations auszuführen, fügt die Stapelansicht eine Höhenbeschränkung von 0 hinzu, die die USSC-Warnung auslöst.


Es gibt mindestens 3 Lösungen für dieses Problem:

  1. Überprüfen Sie vor dem Ausblenden eines Elements in einer Stapelansicht, ob es sich um eine Stapelansicht handelt, und ändern Sie spacingin diesem Fall den Wert auf 0. Dies ist ärgerlich, da Sie den Vorgang umkehren müssen (und den ursprünglichen Abstand berücksichtigen müssen), wenn Sie den Inhalt erneut anzeigen .
  2. Rufen Sie auf, anstatt Elemente in einer Stapelansicht auszublenden removeFromSuperview. Dies ist umso ärgerlicher, als Sie sich beim Umkehren des Vorgangs merken müssen, wo das entfernte Element eingefügt werden soll. Sie können optimieren, indem Sie nur removeArrangedSubview aufrufen und dann ausblenden. Es ist jedoch noch viel Buchhaltung erforderlich.
  3. Verschachtelte spacingStapelansichten (die nicht Null sind ) in eine UIView einschließen. Geben Sie mindestens eine Einschränkung als nicht erforderliche Priorität an (999 oder niedriger). Dies ist die beste Lösung, da Sie keine Buchhaltung durchführen müssen. In meinem Beispiel habe ich bei 1000 zwischen der Stapelansicht und der Wrapper-Ansicht obere, führende und nachfolgende Einschränkungen erstellt und dann eine 999-Einschränkung vom unteren Rand der Stapelansicht zur Wrapper-Ansicht erstellt. Auf diese Weise wird die 999-Einschränkung unterbrochen, wenn die äußere Stapelansicht eine Einschränkung für die Höhe Null erstellt, und die USSC-Warnung wird nicht angezeigt. (Hinweis: Dies ähnelt der Lösung für Sollte die contentView.translatesAutoResizingMaskToConstraints einer UICollectionViewCell-Unterklasse auf gesetzt seinfalse )

Zusammenfassend sind die Gründe für dieses Verhalten:

  1. Apple erstellt automatisch 1000 Prioritätsbeschränkungen für Sie, wenn Sie einer Stapelansicht verwaltete Unteransichten hinzufügen.
  2. Apple erstellt automatisch eine 0-Höhenbeschränkung für Sie, wenn Sie eine Unteransicht einer Stapelansicht ausblenden.

Hätte Apple entweder (1) die Angabe der Priorität von Einschränkungen (insbesondere von Abstandshaltern) oder (2) die Deaktivierung der automatischen Einschränkung zum Ausblenden von UISV zugelassen , wäre dieses Problem leicht zu lösen.

Sinnvoll
quelle
6
Vielen Dank für die super gründliche und hilfreiche Erklärung. Dies scheint jedoch definitiv ein Fehler auf Apples Seite mit Stapelansichten zu sein. Grundsätzlich ist ihre Funktion "Ausblenden" nicht mit ihrer Funktion "Abstand" kompatibel. Irgendwelche Ideen, dass sie dies seitdem behoben haben, oder haben einige Funktionen hinzugefügt, um das Herumhacken mit zusätzlichen Ansichten zu verhindern? (Wieder eine große Aufschlüsselung möglicher Lösungen und stimmen der Eleganz von # 3 zu)
März
1
Muss jedes einzelne UIStackViewKind eines UIStackViewMenschen in ein UIViewoder nur das eingewickelt werden, das Sie verstecken / einblenden möchten?
Adrian
1
Dies fühlt sich wie ein wirklich schlechtes Versehen von Apple an. Vor allem, weil Warnungen und Fehler im Zusammenhang mit der Verwendung von UIStackViewkryptisch und schwer zu verstehen sind.
Bompf
Dies ist ein Lebensretter. Ich hatte ein Problem, bei dem UIStackViews, die zu einer UITableViewCell hinzugefügt wurden, bei jeder Wiederverwendung der Zelle AutoLayout-Fehlerprotokoll-Spam verursachten. Das Einbetten der stackView in eine UIView mit niedriger Priorität für die untere Ankerbedingung hat das Problem behoben. Der View-Debugger zeigt zwar an, dass die Elemente von stackView mehrdeutige Höhen haben, wird jedoch in der App ohne Fehlerprotokoll-Spam ordnungsgemäß angezeigt. DANKE.
Womble
6

In den meisten Fällen kann dieser Fehler durch Verringern der Einschränkungspriorität behoben werden, um Konflikte zu beseitigen.

Luciano Almeida
quelle
Was meinst du? Die Einschränkungen sind alle relativ innerhalb der Ansichten, die gestapelt sind ....
Ben Guild
Es tut mir leid, ich verstehe Ihre Frage nicht richtig, mein Englisch ist so, aber ich denke, dass Einschränkungen relativ zu der Ansicht sind, mit der sie verknüpft sind. Wenn die Ansicht ausgeblendet wird, werden die Einschränkungen inaktiv. Ich bin mir nicht sicher, ob das dein Zweifel ist, aber ich hoffe, ich kann helfen.
Luciano Almeida
Es scheint zu kommen, wenn das Zeug zerquetscht wird oder gerade gezeigt / versteckt wird. In diesem Fall wird es teilweise sichtbar. - Vielleicht ist es notwendig, tatsächlich alle vertikalen Mindestkonstanten zu durchlaufen und zu eliminieren, da sie auf eine Höhe von 0 gequetscht werden können?
Ben Guild
2

Wenn Sie eine Ansicht auf "Ausgeblendet" setzen, UIStackviewwird versucht, sie zu animieren. Wenn Sie diesen Effekt erzielen möchten, müssen Sie die richtige Priorität für die Einschränkungen festlegen, damit sie nicht in Konflikt geraten (wie viele oben vorgeschlagen haben).

Wenn Sie sich jedoch nicht für die Animation interessieren (vielleicht verstecken Sie sie in ViewDidLoad), können Sie einfach festlegen, removeFromSuperviewwas den gleichen Effekt hat, jedoch ohne Probleme mit Einschränkungen, da diese zusammen mit der Ansicht entfernt werden.

Oren
quelle
1

Basierend auf der Antwort von @ Senseful gibt es hier eine UIStackView-Erweiterung, mit der eine Stapelansicht in eine Ansicht eingeschlossen und die von ihm empfohlenen Einschränkungen angewendet werden können:

/// 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
}

Anstatt Ihre hinzuzufügen stackView, verwenden Sie stackView.wrapped().

Ben Packard
quelle
1

Stellen Sie zunächst, wie andere vorgeschlagen haben, sicher, dass die Einschränkungen, die Sie steuern können, dh nicht die Einschränkungen, die UIStackView inhärent sind, auf Priorität 999 festgelegt sind, damit sie überschrieben werden können, wenn die Ansicht ausgeblendet ist.

Wenn das Problem weiterhin auftritt, liegt das Problem wahrscheinlich an den Abständen in den ausgeblendeten StackViews. Meine Lösung bestand darin, ein UIView als Abstandshalter hinzuzufügen und den UIStackView-Abstand auf Null zu setzen. Stellen Sie dann die Einschränkungen für View.height oder View.width (abhängig von einem vertikalen oder horizontalen Stapel) auf den Abstand der StackView ein.

Passen Sie dann die Prioritäten für Inhaltsumarmung und Inhaltskomprimierungsbeständigkeit Ihrer neu hinzugefügten Ansichten an. Möglicherweise müssen Sie auch die Verteilung der übergeordneten StackView ändern.

All dies kann in Interface Builder durchgeführt werden. Möglicherweise müssen Sie zusätzlich einige der neu hinzugefügten Ansichten programmgesteuert ein- oder ausblenden, damit Sie keine unerwünschten Abstände haben.

Peter Coyle
quelle
1

Ich habe kürzlich mit automatischen Layoutfehlern gerungen, als ich a versteckte UIStackView. Anstatt ein paar Bücher zu führen und Stapel einzupacken UIViews, habe ich mich dafür entschieden, eine Steckdose für meine parentStackViewund Steckdosen für die Kinder zu schaffen, die ich verstecken / einblenden möchte.

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

Im Storyboard sieht mein parentStack folgendermaßen aus:

Geben Sie hier die Bildbeschreibung ein

Es hat 4 Kinder und jedes der Kinder hat eine Reihe von Stapelansichten in sich. Wenn Sie eine Stapelansicht ausblenden und UI-Elemente enthalten, die ebenfalls Stapelansichten sind, wird ein Strom von Fehlern beim automatischen Layout angezeigt. Anstatt mich zu verstecken, entschied ich mich, sie zu entfernen.

In meinem Beispiel parentStackViewsenthält es ein Array der 4 Elemente: Top Stack View, StackViewNumber1, Stack View Number 2 und Stop Button. Ihre Indizes in arrangedSubviewssind 0, 1, 2 bzw. 3. Wenn ich eine ausblenden möchte, entferne ich sie einfach aus dem parentStackView's arrangedSubviewsArray. Da es nicht schwach ist, bleibt es im Speicher und Sie können es später einfach wieder auf den gewünschten Index setzen. Ich initialisiere es nicht neu, so dass es nur so lange hängt, bis es benötigt wird, aber das Gedächtnis nicht aufbläht.

Im Grunde können Sie ...

1) Ziehen Sie IBOutlets für Ihren übergeordneten Stapel und die untergeordneten Elemente, die Sie ein- oder ausblenden möchten, in das Storyboard.

2) Wenn Sie sie ausblenden möchten, entfernen Sie den Stapel, den Sie aus dem parentStackView's arrangedSubviewsArray ausblenden möchten .

3) Rufen Sie self.view.layoutIfNeeded()mit an UIView.animateWithDuration.

Beachten Sie, dass die letzten beiden StackViews dies nicht sind weak. Sie müssen sie aufbewahren, wenn Sie sie einblenden.

Angenommen, ich möchte stackViewNumber2 ausblenden:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Dann animiere es:

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

Wenn Sie ein stackViewNumber2späteres "einblenden" möchten , können Sie es einfach in den gewünschten parentStackView arrangedSubViewsIndex einfügen und das Update animieren.

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)

Ich fand das viel einfacher als die Buchhaltung von Einschränkungen, das Herumspielen mit Prioritäten usw.

Wenn Sie etwas haben, das Sie standardmäßig ausblenden möchten, können Sie es einfach im Storyboard auslegen und entfernen viewDidLoadund aktualisieren, ohne dass die Animation verwendet wird view.layoutIfNeeded().

Adrian
quelle
1

Bei eingebetteten Stapelansichten traten dieselben Fehler auf, obwohl zur Laufzeit alles einwandfrei funktionierte.

Ich habe die Einschränkungsfehler behoben, indem ich zuerst alle Unterstapelansichten ausgeblendet habe (Einstellung isHidden = true), bevor ich die übergeordnete Stapelansicht ausgeblendet habe.

Dies hatte nicht die Komplexität, untergeordnete Ansichten zu entfernen und einen Index zu verwalten, wenn sie wieder hinzugefügt werden mussten.

Hoffe das hilft.

Will Stevens
quelle
1

Senseful hat eine hervorragende Antwort auf die Wurzel des obigen Problems gegeben, sodass ich direkt zur Lösung komme.

Alles, was Sie tun müssen, ist, die Priorität aller StackView-Einschränkungen auf weniger als 1000 zu setzen (999 erledigt die Arbeit). Wenn die Stapelansicht beispielsweise links, rechts, oben und unten auf ihre Übersicht beschränkt ist, sollten alle 4 Einschränkungen eine Priorität von weniger als 1000 haben.

Linh Ta
quelle
0

Möglicherweise haben Sie beim Arbeiten mit einer bestimmten Größenklasse (z. B. wCompact hRegular) eine Einschränkung erstellt und dann beim Wechsel zu einer anderen Größenklasse (z. B. wAny hAny) ein Duplikat erstellt. Überprüfen Sie die Einschränkungen der UI-Objekte in verschiedenen Größenklassen und prüfen Sie, ob Anomalien mit den Einschränkungen vorliegen. Sie sollten die roten Linien sehen, die auf kollidierende Einschränkungen hinweisen. Ich kann kein Bild einfügen, bis ich 10 Reputationspunkte bekomme. Entschuldigung: /

FM
quelle
Oh ok. Aber ich habe diesen Fehler erhalten, als ich den Fall hatte, dass ich drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…
FM
Ja, ich sehe definitiv kein Rot, wenn ich die Größenklassen im Interface Builder umschalte. Ich habe nur die Größe "Beliebig" verwendet.
Ben Guild
0

Ich wollte ganze UIStackViews gleichzeitig ausblenden, bekam aber die gleichen Fehler wie das OP. Dies hat das Problem für mich behoben:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}
sp00ky
quelle
Dies hat bei mir nicht funktioniert, da sich die Auto-Layout-Engine beschwert, wenn Sie eine erforderliche Einschränkung ( priority = 1000) in nicht erforderlich ( priority <= 999) ändern .
Sinnvoll
0

Ich hatte eine Reihe von Knöpfen mit Höhenbeschränkung. Dies geschieht, wenn eine Schaltfläche ausgeblendet ist. Durch Festlegen der Priorität der Höhenbeschränkung dieser Schaltflächen auf 999 wurde das Problem behoben.

Niklas
quelle
-2

Dieser Fehler hat nichts mit UIStackView zu tun. Dies passiert, wenn Sie Konfliktbeschränkungen mit denselben Prioritäten haben. Wenn Sie beispielsweise eine Einschränkung haben, die besagt, dass die Breite Ihrer Ansicht 100 beträgt, und eine andere Einschränkung gleichzeitig angibt, dass die Breite der Ansicht 25% des Containers beträgt. Offensichtlich gibt es zwei widersprüchliche Einschränkungen. Die Lösung besteht darin, einen von ihnen zu löschen.

William Kinaan
quelle
-3

NOP mit [mySubView removeFromSuperview]. Ich hoffe es könnte jemandem helfen :)

Grégoire GUYON
quelle
Entschuldigung, was ist NOP?
Pang