UIStackView "Impossibile soddisfare i vincoli simultaneamente" sulle viste nascoste "schiacciate"


95

Quando le mie "righe" UIStackView vengono schiacciate, generano AutoLayoutavvisi. Tuttavia, vengono visualizzati correttamente e nient'altro è sbagliato oltre a questi tipi di registrazioni:

Impossibile soddisfare i vincoli contemporaneamente. Probabilmente almeno uno dei vincoli nell'elenco seguente è uno che non si desidera. Prova questo: (1) guarda ogni vincolo e cerca di capire quale non ti aspetti; (2) trova il codice che ha aggiunto il vincolo o i vincoli indesiderati e correggilo. (Nota: se vedi NSAutoresizingMaskLayoutConstraintsche non capisci, fai riferimento alla documentazione della UIViewproprietà translatesAutoresizingMaskIntoConstraints) (

Quindi, non sono ancora sicuro di come risolverlo, ma non sembra rompere nulla oltre a essere fastidioso.

Qualcuno sa come risolverlo? È interessante notare che i vincoli di layout sono contrassegnati abbastanza spesso con 'UISV-hiding' , indicando che forse dovrebbe ignorare i minimi di altezza per le sottoview o qualcosa del genere in questo caso?


1
Questo sembra essere stato risolto in iOS11, senza ricevere alcun avviso qui
trapper

Risposte:


204

Si verifica questo problema perché quando si imposta una vista secondaria dall'interno UIStackViewa nascosta, prima vincolerà la sua altezza a zero per animarla.

Ho ricevuto il seguente errore:

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.

Quello che stavo cercando di fare era inserire un elemento UIViewall'interno di my UIStackViewche conteneva un UISegmentedControlriquadro di 8 punti su ciascun bordo.

Quando l'ho impostato su nascosto, provava a vincolare la visualizzazione del contenitore a un'altezza zero ma poiché ho una serie di vincoli dall'alto verso il basso, si è verificato un conflitto.

Per risolvere il problema, ho modificato la priorità dei vincoli 8pt top e bottom da 1000 a 999 in modo che il UISV-hidingvincolo possa quindi avere la priorità se necessario.


va notato che sembra che se hai solo un vincolo di altezza o larghezza su questi e riduci la loro priorità, non funzionerà. È necessario rimuovere l'altezza / larghezza e aggiungere le parti inferiori finali finali superiori, quindi impostare la loro priorità come inferiore e funziona
bolnad

4
Anche cambiare le priorità ha funzionato per me. Inoltre, rimozione di eventuali vincoli in eccesso (in grigio) che sono stati copiati accidentalmente da classi di dimensioni inutilizzate. SUGGERIMENTO IMPORTANTE: per eseguire più facilmente il debug di questi problemi, impostare una stringa IDENTIFER su ciascun vincolo. Quindi puoi vedere quale vincolo era cattivo nel messaggio di debug.
Womble

3
Nel mio caso, devo solo abbassare la priorità sull'altezza e funziona.
pixelfreak

Quel suggerimento IDENTIFICATORE è fantastico! Mi sono sempre chiesto come dare i vincoli nei nomi dei messaggi di debug, ho sempre cercato di aggiungere qualcosa alla vista piuttosto che il vincolo stesso. Grazie @ Womble!
Ryan

Grazie! La priorità da 1000 a 999 ha funzionato.Xcode: Versione 8.3.3 (8E3004b)
Michael Garito

51

Avevo un problema simile che non era facile da risolvere. Nel mio caso, avevo una vista stack incorporata in una vista stack. L'UIStackView interno aveva due etichette e una spaziatura diversa da zero specificata.

Quando chiami addArrangedSubview (), creerà automaticamente vincoli simili ai seguenti:

V:|[innerStackView]|              | = outerStackView

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

Ora, quando provi a nascondere innerStackView, ricevi un avviso di vincoli ambigui.

Per capire perché, vediamo prima perché questo non accade quando innerStackView.spacingè uguale a 0. Quando chiami innerStackView.hidden = true, @liamnichols aveva ragione ... outerStackViewintercetterà magicamente questa chiamata e creerà un vincolo per nascondere l'UISV di0 altezza con priorità 1000 (obbligatorio). Presumibilmente questo è per consentire agli elementi nella vista stack di essere animati fuori dalla vista nel caso in cui il codice nascosto venga chiamato all'interno di un blocco. Sfortunatamente, non sembra esserci un modo per impedire che questo vincolo venga aggiunto. Tuttavia, non verrà visualizzato l'avviso "Impossibile soddisfare i vincoli contemporaneamente" (USSC), poiché si verifica quanto segue:UIView.animationWithDuration()

  1. L'altezza di label1 è impostata su 0
  2. la spaziatura tra le due etichette era già definita come 0
  3. L'altezza di label2 è impostata su 0
  4. l'altezza di innerStackView è impostata su 0

È chiaro che questi 4 vincoli possono essere soddisfatti. La vista stack trasforma semplicemente tutto in un pixel di altezza 0.

Ora, tornando all'esempio con bug, se impostiamo spacingsu 2, ora abbiamo questi vincoli:

  1. L'altezza di label1 è impostata su 0
  2. la spaziatura tra le due etichette è stata creata automaticamente dalla vista pila come 2 pixel di altezza con priorità 1000.
  3. L'altezza di label2 è impostata su 0
  4. l'altezza di innerStackView è impostata su 0

La vista stack non può essere alta sia 0 pixel che i suoi contenuti alti 2 pixel. I vincoli non possono essere soddisfatti.

Nota: puoi vedere questo comportamento con un esempio più semplice. Aggiungi semplicemente una vista UIV a una vista pila come una vista secondaria organizzata. Quindi imposta un vincolo di altezza su quella UIView con priorità 1000. Ora prova a chiamare hide su quello.

Nota: per qualsiasi motivo, ciò è accaduto solo quando la mia visualizzazione stack era una sottoview di un UICollectionViewCell o UITableViewCell. Tuttavia, è comunque possibile riprodurre questo comportamento all'esterno di una cella chiamando innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)il ciclo di esecuzione successivo dopo aver nascosto la visualizzazione dello stack interno.

Nota: anche se provi a eseguire il codice in un UIView.performWithoutAnimations, la visualizzazione stack aggiungerà comunque un vincolo di altezza 0 che causerà l'avviso USSC.


Esistono almeno 3 soluzioni a questo problema:

  1. Prima di nascondere qualsiasi elemento in una vista a pila, controlla se si tratta di una vista a pila e, in tal caso, cambia il spacinga 0. Questo è fastidioso perché devi invertire il processo (e ricordare la spaziatura originale) ogni volta che mostri di nuovo il contenuto.
  2. Invece di nascondere gli elementi in una visualizzazione stack, chiama removeFromSuperview. Questo è ancora più fastidioso poiché quando si inverte il processo, è necessario ricordare dove inserire l'elemento rimosso. È possibile ottimizzare solo chiamando removeArrangedSubview e poi nascondendosi, ma c'è ancora molta contabilità che deve essere fatta.
  3. Racchiudi le viste stack nidificate (che hanno un valore diverso da zero spacing) in un UIView. Specificare almeno un vincolo come priorità non richiesta (999 o inferiore). Questa è la soluzione migliore poiché non devi fare alcuna contabilità. Nel mio esempio, ho creato i vincoli superiore, iniziale e finale a 1000 tra la vista stack e la vista wrapper, quindi ho creato un vincolo 999 dalla parte inferiore della vista stack alla vista wrapper. In questo modo, quando la vista pila esterna crea un vincolo di altezza zero, il vincolo 999 viene interrotto e non viene visualizzato l'avviso USSC. (Nota: questo è simile alla soluzione a Dovrebbe essere impostato il contentView.translatesAutoResizingMaskToConstraints di una sottoclasse UICollectionViewCellfalse )

In sintesi, i motivi per cui ottieni questo comportamento sono:

  1. Apple crea automaticamente 1000 vincoli di priorità per te quando aggiungi viste secondarie gestite a una visualizzazione stack.
  2. Apple crea automaticamente un vincolo di altezza 0 quando nascondi una vista secondaria di una vista sovrapposta.

Se Apple (1) ti avesse permesso di specificare la priorità dei vincoli (specialmente degli spaziatori), o (2) ti avesse permesso di rinunciare al vincolo di occultamento automatico dell'UISV , questo problema sarebbe stato facilmente risolto.


6
Grazie per la spiegazione super completa e utile. Questo sicuramente tuttavia sembra un bug da parte di Apple con le viste stack. Fondamentalmente la loro funzione di "occultamento" è incompatibile con la loro funzione di "spaziatura". Qualche idea sul fatto che abbiano risolto il problema da allora o hanno aggiunto alcune funzionalità per impedire l'hacking con viste contenenti extra? (Ancora una volta, grande ripartizione delle potenziali soluzioni e sono d'accordo sull'eleganza del n. 3)
Marchy

1
Ogni singolo UIStackViewfiglio di un UIStackViewbisogno di essere avvolto in un UIViewo solo quello che stai cercando di nascondere / scoprire?
Adrian

1
Questa sembra davvero una brutta svista da parte di Apple. Soprattutto perché gli avvisi e gli errori che circondano l'uso di UIStackViewtendono ad essere criptici e difficili da capire.
bompf

Questo è un vero toccasana. Ho riscontrato un problema in cui UIStackViews aggiunto a un UITableViewCell causava spam nel log degli errori di AutoLayout, ogni volta che la cella veniva riutilizzata. Incorporando lo stackView in una UIView con la priorità del vincolo di ancoraggio inferiore impostata su bassa, ha risolto il problema. Fa sì che il debugger della vista mostri gli elementi dello stackView come aventi altezze ambigue, ma vengono visualizzati correttamente nell'app, senza spam nel log degli errori. GRAZIE.
Womble

6

Nella maggior parte dei casi, questo errore può essere risolto abbassando la priorità dei vincoli per eliminare i conflitti.


Cosa intendi? I vincoli sono tutti relativi all'interno delle viste che sono impilate ....
Ben Guild

Mi dispiace, non capisco bene la tua domanda, il mio inglese è così così, ma quello che penso è che i vincoli sono relativi alla vista a cui è associato, se la vista diventa nascosta i vincoli diventano inattivi. Non sono sicuro che questo sia il tuo dubbio, ma spero di poterti aiutare.
Luciano Almeida

Sembra provenire da quando la roba è schiacciata o in procinto di essere mostrata / nascosta. In questo caso, diventa parzialmente visibile. - Forse è necessario effettivamente passare attraverso ed eliminare eventuali costanti verticali minime perché può essere schiacciato a 0 altezza?
Ben Guild

2

Quando si imposta una vista su nascosta, il UIStackviewproverà ad animarla. Se vuoi quell'effetto, dovrai impostare la giusta priorità per i vincoli in modo che non entrino in conflitto (come molti hanno suggerito sopra).

Tuttavia, se non ti interessa l'animazione (forse la stai nascondendo in ViewDidLoad), puoi semplice removeFromSuperviewche avrà lo stesso effetto ma senza problemi con i vincoli poiché quelli verranno rimossi insieme alla vista.


1

Sulla base della risposta di @ Senseful, ecco un'estensione UIStackView per racchiudere una vista stack in una vista e applicare i vincoli che lui o lei consiglia:

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

Invece di aggiungere il tuo stackView, usa stackView.wrapped().


1

Innanzitutto, come altri hanno suggerito, assicurati che i vincoli che puoi controllare, cioè non i vincoli inerenti a UIStackView, siano impostati sulla priorità 999 in modo che possano essere sovrascritti quando la vista è nascosta.

Se il problema persiste, è probabile che il problema sia dovuto alla spaziatura negli StackView nascosti. La mia soluzione era aggiungere un UIView come spaziatore e impostare la spaziatura UIStackView su zero. Quindi imposta i vincoli View.height o View.width (a seconda di uno stack verticale o orizzontale) sulla spaziatura di StackView.

Quindi regola le priorità dell'abbraccio del contenuto e della resistenza alla compressione del contenuto delle visualizzazioni appena aggiunte. Potrebbe essere necessario modificare anche la distribuzione dello StackView padre.

Tutto quanto sopra può essere fatto in Interface Builder. Potrebbe inoltre essere necessario nascondere / mostrare alcune delle viste appena aggiunte a livello di programmazione in modo da non avere spazi indesiderati.


1

Recentemente ho lottato con errori di layout automatico nascondendo un file UIStackView. Piuttosto che tenere un mucchio di libri e impacchettare pile UIViews, ho deciso di creare uno sbocco per il mio parentStackViewe punti vendita per i bambini che voglio nascondere / mostrare.

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

Nello storyboard, ecco come appare il mio genitoreStack:

inserisci qui la descrizione dell'immagine

Ha 4 bambini e ciascuno di essi ha una serie di viste in pila al loro interno. Quando nascondi una vista stack, se ha elementi dell'interfaccia utente che sono anche viste stack, vedrai un flusso di errori di layout automatico. Invece di nasconderli, ho deciso di rimuoverli.

Nel mio esempio, parentStackViewscontiene una matrice dei 4 elementi: Top Stack View, StackViewNumber1, Stack View Number 2 e Stop Button. I loro indici in arrangedSubviewssono rispettivamente 0, 1, 2 e 3. Quando voglio nasconderne uno, lo rimuovo semplicemente parentStackView's arrangedSubviewsdall'array. Poiché non è debole, rimane nella memoria e puoi semplicemente rimetterlo all'indice desiderato in seguito. Non lo sto reinizializzando, quindi resta in sospeso finché non è necessario, ma non gonfia la memoria.

Quindi, in pratica, puoi ...

1) Trascina nello storyboard IBOutlets per lo stack genitore e per i figli che desideri nascondere / mostrare.

2) Quando vuoi nasconderli, rimuovi lo stack che vuoi nascondere parentStackView's arrangedSubviewsdall'array.

3) Chiama self.view.layoutIfNeeded()con UIView.animateWithDuration.

Nota che gli ultimi due stackView non lo sono weak. Devi tenerli in giro per quando li riveli.

Diciamo che voglio nascondere stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Quindi animalo:

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

Se vuoi "scoprire" un stackViewNumber2secondo, puoi semplicemente inserirlo parentStackView arrangedSubViewsnell'indice desiderato e animare l'aggiornamento.

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)

Ho scoperto che è molto più facile che fare la contabilità sui vincoli, giocherellare con le priorità, ecc.

Se hai qualcosa che desideri nascondere per impostazione predefinita, puoi semplicemente posizionarlo sullo storyboard e rimuoverlo viewDidLoade aggiornarlo senza che l'animazione utilizzi view.layoutIfNeeded().


1

Ho riscontrato gli stessi errori con le viste stack integrate, anche se tutto ha funzionato correttamente in fase di esecuzione.

Ho risolto gli errori di vincolo nascondendo prima tutte le viste dello stack secondario (impostazione isHidden = true) prima di nascondere la visualizzazione dello stack principale.

Fare questo non ha avuto tutta la complessità di rimuovere le viste suddivise, mantenendo un indice per quando è necessario aggiungerle di nuovo.

Spero che questo ti aiuti.


1

Senseful ha fornito un'ottima risposta alla radice del problema sopra, quindi andrò direttamente alla soluzione.

Tutto quello che devi fare è impostare la priorità di tutti i vincoli di stackView inferiore a 1000 (999 farà il lavoro). Ad esempio, se lo stackView è vincolato a sinistra, destra, in alto e in basso alla sua superview, tutti e 4 i vincoli dovrebbero avere la priorità inferiore a 1000.


0

Potresti aver creato un vincolo mentre lavoravi con una certa classe di dimensioni (es: wCompact hRegular) e quindi hai creato un duplicato quando sei passato a un'altra classe di dimensioni (es: wAny hAny). controllare i vincoli degli oggetti dell'interfaccia utente in diverse classi di dimensioni e vedere se ci sono anomalie con i vincoli. dovresti vedere le linee rosse che indicano i vincoli in collisione. Non posso mettere una foto finché non ottengo 10 punti reputazione, mi dispiace: /


Oh va bene. ma ho ricevuto quell'errore quando ho avuto il caso che ho descritto drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…
FM

Sì, sicuramente non vedo alcun rosso cambiando le classi di dimensioni nel generatore di interfacce. Ho usato solo la dimensione "Qualsiasi".
Ben Guild

0

Volevo nascondere interi UIStackView alla volta ma stavo ottenendo gli stessi errori dell'OP, questo lo ha risolto per me:

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

Questo non ha funzionato per me perché il motore di layout automatico si lamenta quando si modifica un vincolo richiesto ( priority = 1000) in non richiesto ( priority <= 999).
Senseful

0

Avevo una fila di pulsanti con vincolo di altezza. Questo accade quando un pulsante è nascosto. L'impostazione della priorità del vincolo di altezza dei pulsanti su 999 ha risolto il problema.


-2

Questo errore non ha nulla a che fare con UIStackView. Succede quando hai vincoli di conflitto con le stesse priorità. Ad esempio, se si dispone di un vincolo, si afferma che la larghezza della vista è 100 e allo stesso tempo si ha un altro vincolo che la larghezza della vista è il 25% del suo contenitore. Ovviamente ci sono due vincoli contrastanti. La soluzione è eliminarne uno.


-3

NOP con [mySubView removeFromSuperview]. Spero che possa aiutare qualcuno :)


Non ho capito, cos'è NOP?
Pang
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.