it-swarm.com.de

Verschachtelte UIStackViews Gebrochene Einschränkungen

Ich habe eine komplexe Ansichtshierarchie, die im Interface Builder mit verschachtelten UIStackViews erstellt wurde. Ich bekomme jedes Mal "unbefriedigende Einschränkungen", wenn ich einige meiner inneren Stapelansichten verstecke. Ich habe es bis hierher verfolgt:

(
    "<NSLayoutConstraint:0x1396632d0 'UISV-canvas-connection' UIStackView:0x1392c5020.top == UILabel:0x13960cd30'Also available on iBooks'.top>",
    "<NSLayoutConstraint:0x139663470 'UISV-canvas-connection' V:[UIButton:0x139554f80]-(0)-|   (Names: '|':UIStackView:0x1392c5020 )>",
    "<NSLayoutConstraint:0x139552350 'UISV-hiding' V:[UIStackView:0x1392c5020(0)]>",
    "<NSLayoutConstraint:0x139663890 'UISV-spacing' V:[UILabel:0x13960cd30'Also available on iBooks']-(8)-[UIButton:0x139554f80]>"
)

Insbesondere die UISV-spacing-Einschränkung: Beim Ausblenden eines UIStackView-Objekts erhält seine hohe Einschränkung eine 0-Konstante. Dies scheint jedoch mit der Abstandsbeschränkung der inneren Stapelansicht zu kollidieren: Sie erfordert 8 Punkte zwischen my Label und Button, was mit der Ausblendungsbeschränkung unvereinbar ist Einschränkungen abstürzen.

Gibt es da einen Weg? Ich habe versucht, alle inneren StackViews der Ansicht mit verborgenen Stapeln rekursiv zu verbergen. Dies führt jedoch zu seltsamen Animationen, bei denen der Inhalt aus dem Bildschirm herausfließt und schwere FPS-Abstürze verursacht, ohne das Problem zu beheben.

23
Alex Popov

Idealerweise könnten wir die Priorität der UISV-spacing-Einschränkung einfach auf einen niedrigeren Wert setzen, aber es scheint keine Möglichkeit zu geben, dies zu tun. :)

Es ist erfolgreich, die spacing-Eigenschaft der verschachtelten Stapelansichten auf 0 zu setzen, bevor sie ausgeblendet wird, und den richtigen Wert wiederherzustellen, nachdem sie wieder sichtbar gemacht wurde.

Ich denke, dies würde rekursiv auf verschachtelten Stack-Ansichten funktionieren. Sie können den ursprünglichen Wert der spacing-Eigenschaft in einem Wörterbuch speichern und später wiederherstellen.

Mein Projekt hat nur eine einzige Schachtelungsebene, daher bin ich mir nicht sicher, ob dies zu FPS-Problemen führen würde. Solange Sie die Abstandsänderungen nicht animieren, glaube ich nicht, dass dies zu viel Treffer verursachen würde.

13
Cory Juhlin

Dies ist ein bekanntes Problem beim Ausblenden verschachtelter Stapelansichten.

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

  1. Ändern Sie den Abstand auf 0, aber Sie müssen sich den vorherigen Abstandswert merken.
  2. Rufen Sie innerStackView.removeFromSuperview() auf. Dann müssen Sie jedoch wissen, wo die Stapelansicht eingefügt werden soll.
  3. Packen Sie die Stapelansicht in eine UIView mit mindestens einer 999-Einschränkung ein. Z.B. oben @ 1000, führend @ 1000, nachlaufend @ 1000, unten @ 999.

Die 3. Option ist meiner Meinung nach die beste. Weitere Informationen zu diesem Problem, warum es auftritt, zu den verschiedenen Lösungen und zur Implementierung von Lösung 3 finden Sie unter meine Antwort auf eine ähnliche Frage .

17
Senseful

Ich habe ein ähnliches Problem mit dem UISV-Hiding festgestellt. Für mich bestand die Lösung darin, die Prioritäten meiner eigenen Einschränkungen von Required (1000) auf etwas weniger zu reduzieren. Wenn UISV-Ausblendungsbedingungen hinzugefügt werden, haben sie Priorität und die Bedingungen stimmen nicht mehr überein.

16
Jaanus

Sie haben also folgendes:

 broken animation

Und das Problem ist, wenn Sie den inneren Stapel zum ersten Mal minimieren, erhalten Sie automatische Layoutfehler:

2017-07-02 15:40:02.377297-0500 nestedStackViews[17331:1727436] [LayoutConstraints] 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:0x62800008ce90 'UISV-canvas-connection' UIStackView:0x7fa57a70fce0.top == UILabel:0x7fa57a70ffb0'Top Label of Inner Stack'.top   (active)>",
    "<NSLayoutConstraint:0x62800008cf30 'UISV-canvas-connection' V:[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']-(0)-|   (active, names: '|':UIStackView:0x7fa57a70fce0 )>",
    "<NSLayoutConstraint:0x62000008bc70 'UISV-hiding' UIStackView:0x7fa57a70fce0.height == 0   (active)>",
    "<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']   (active)>

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.

Wie Sie bemerkt haben, besteht das Problem darin, dass die äußere Stapelansicht eine Einschränkung height = 0 auf die innere Stapelansicht anwendet. Dies steht im Konflikt mit der 8-Punkt-Auffüllbeschränkung, die von der inneren Stapelansicht zwischen den eigenen Unteransichten angewendet wird. Beide Bedingungen können nicht gleichzeitig erfüllt werden.

Ich glaube, dass die äußere Stapelansicht diese Einschränkung von height = 0 verwendet, da sie beim Animieren besser aussieht als nur die innere Ansicht auszublenden, ohne sie zuerst zu verkleinern.

Es gibt eine einfache Lösung für dieses Problem: Wickeln Sie die innere Stapelansicht in eine einfache UIView und blenden Sie den Wrapper aus. Ich werde es zeigen.

Hier ist die Szenenbeschreibung für die oben abgebrochene Version:

 broken outline

Wählen Sie die innere Stapelansicht aus, um das Problem zu beheben. Wählen Sie in der Menüleiste Editor> Einbetten in> Ansicht:

 embed in view

Interface Builder hat dabei eine Breitenbeschränkung für die Wrapper-Ansicht erstellt. Löschen Sie diese Breitenbeschränkung:

 delete width constraint

Als Nächstes erstellen Sie Einschränkungen zwischen allen vier Kanten des Wrappers und der inneren Stapelansicht:

 create constraints

Zu diesem Zeitpunkt ist das Layout zur Laufzeit tatsächlich korrekt, aber der Interface Builder zeichnet es falsch. Sie können das Problem beheben, indem Sie die vertikalen Umarmungsprioritäten der Kinder des inneren Stapels höher einstellen. Ich habe sie auf 800 gesetzt:

 hugging priorities

Wir haben das unbefriedigende Einschränkungsproblem zu diesem Zeitpunkt noch nicht behoben. Suchen Sie dazu die soeben erstellte untere Einschränkung und setzen Sie deren Priorität auf weniger als erforderlich. Ändern wir es auf 800:

 change bottom constraint priority

Schließlich hatten Sie vermutlich einen Auslass in Ihrem View-Controller, der mit der inneren Stapelansicht verbunden war, da Sie seine hidden-Eigenschaft geändert haben. Ändern Sie diesen Ausgang, um eine Verbindung zur Wrapper-Ansicht anstelle der inneren Stapelansicht herzustellen. Wenn der Typ Ihrer Verkaufsstelle UIStackView ist, müssen Sie ihn in UIView ändern. Meiner war bereits vom Typ UIView, also habe ich es einfach im Storyboard wieder verbunden:

 change outlet

Wenn Sie nun die hidden -Eigenschaft der Wrapper-Ansicht umschalten, wird die Stack-Ansicht scheinbar zusammengeklappt, und es werden keine unerfüllbaren Einschränkungswarnungen angezeigt. Es sieht praktisch identisch aus, also werde ich mich nicht darum kümmern, ein weiteres GIF der App zu veröffentlichen.

Sie finden mein Testprojekt in diesem github-Repository .

15
rob mayoff

Hier ist die Implementierung von Sensefuls Vorschlag Nr. 3, der als Swift 3-Klasse mit SnapKit-Einschränkungen geschrieben wurde. Ich habe auch versucht, die Eigenschaften zu überschreiben, habe es aber nie ohne Warnungen zum Laufen gebracht, also bleibe ich mit UIStackView:

class NestableStackView: UIView {
    private var actualStackView = UIStackView()

    override init(frame: CGRect) {
        super.init(frame: frame);
        addSubview(actualStackView);
        actualStackView.snp.makeConstraints { (make) in
            // Lower edges priority to allow hiding when spacing > 0
            make.edges.equalToSuperview().priority(999);
        }
    }

    convenience init() {
        self.init(frame: CGRect.zero);
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func addArrangedSubview(_ view: UIView) {
        actualStackView.addArrangedSubview(view);
    }

    func removeArrangedSubview(_ view: UIView) {
        actualStackView.removeArrangedSubview(view);
    }

    var axis: UILayoutConstraintAxis {
        get {
            return actualStackView.axis;
        }
        set {
            actualStackView.axis = newValue;
        }
    }

    open var distribution: UIStackViewDistribution {
        get {
            return actualStackView.distribution;
        }
        set {
            actualStackView.distribution = newValue;
        }
    }

    var alignment: UIStackViewAlignment {
        get {
            return actualStackView.alignment;
        }
        set {
            actualStackView.alignment = newValue;
        }
    }

    var spacing: CGFloat {
        get {
            return actualStackView.spacing;
        }
        set {
            actualStackView.spacing = newValue;
        }
    }
}
0
Antti

Ein anderer Ansatz

Versuchen Sie, geschachtelte UIStackViews zu vermeiden. Ich liebe sie und baue fast alles damit auf. Da ich jedoch erkannte, dass sie heimlich Einschränkungen hinzufügen, versuche ich, sie nur auf der höchsten Ebene zu verwenden und, soweit möglich, nicht verschachtelt. Auf diese Weise kann ich die 2. höchste Priorität .defaultHigh für die Abstandsbeschränkung angeben, die meine Warnungen auflöst.

Diese Priorität reicht aus, um die meisten Layoutprobleme zu vermeiden. 

Natürlich müssen Sie einige weitere Einschränkungen angeben, aber auf diese Weise haben Sie die vollständige Kontrolle über sie und machen Ihr Ansichtslayout explizit.

0
blackjacx