Che cos'è NSLayoutConstraint "UIView-Encapsulated-Layout-Height" e come devo fare per costringerlo a ricalcolare in modo pulito?


262

Ho una UITableViewcorsa sotto iOS 8 e sto usando le altezze delle celle automatiche dai vincoli in uno storyboard.

Una delle mie celle ne contiene una singola UITextViewe ne ho bisogno per contrarmi ed espandersi in base all'input dell'utente: tocca per ridurre / espandere il testo.

Lo sto facendo aggiungendo un vincolo di runtime alla vista di testo e modificando la costante sul vincolo in risposta agli eventi dell'utente:

-(void)collapse:(BOOL)collapse; {

    _collapsed = collapse;

    if(collapse)
        [_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0
    else
        [_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]];

    [self setNeedsUpdateConstraints];

}

Ogni volta che lo faccio, lo avvolgo negli tableViewaggiornamenti e chiamo [tableView setNeedsUpdateConstraints]:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView setNeedsUpdateConstraints];
// I have also tried 
// [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
// with exactly the same results.

[tableView endUpdates];

Quando lo faccio, la mia cella si espande (e si anima mentre lo fa) ma ricevo un avviso sui vincoli:

2014-07-31 13:29:51.792 OneFlatEarth[5505:730175] 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:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>",

    "<NSLayoutConstraint:0x7f94dced2260 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']-(15)-|   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced2350 V:|-(6)-[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced6480 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f94de5773a0(91)]>"
 )

Will attempt to recover by breaking constraint 

<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>

388 è la mia altezza calcolata, gli altri vincoli UITextViewsono i miei da Xcode / IB.

L'ultimo mi dà fastidio - suppongo che UIView-Encapsulated-Layout-Heightsia l'altezza calcolata della cella quando viene renderizzata per la prima volta ((ho impostato la mia UITextViewaltezza su> = 70,0), tuttavia non sembra giusto che questo vincolo derivato abbia la precedenza su un cnstraint utente aggiornato.

Peggio ancora, anche se il codice del layout dice che sta cercando di rompere il mio vincolo di altezza, non lo fa - continua a ricalcolare l'altezza della cella e tutto disegna come vorrei.

Quindi, cos'è NSLayoutConstraint UIView-Encapsulated-Layout-Height(suppongo sia l'altezza calcolata per il dimensionamento automatico delle celle) e come devo fare per costringerlo a ricalcolare in modo pulito?


3
cross postato sui forum degli sviluppatori
Rog

3
Ho risolto un problema simile nel modo seguente e funziona su iOS 7/8. 1) Abbassa una delle priorità del vincolo a 750. Proverei il 1o o il 2o 2) Nella sottoclasse di celle nel set awakeFromNib self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;. Penso che l'impostazione iniziale della maschera di ridimensionamento automatico impedisca di aggiungere l'ultimo vincolo. Ho trovato questa soluzione qui: github.com/wordpress-mobile/WordPress-iOS/commit/…
Jesse

3
@RogerNolan, qualche novità? Ho riscontrato lo stesso problema giocando con Auto-layout in Interface Builder. Alcune celle causano questo problema, altre no.
orkenstein,

3
Non penso che l'aggiunta di un segno di ridimensionamento automatico sia una buona soluzione.
Rog

1
@RogerNolan stai generando questa cella in IB prima di eseguire queste modifiche al layout? Ho eseguito il debug dello stesso problema ma non ho aggiunto alcun vincolo aggiuntivo. Sono riuscito a sopprimere l'avvertimento ricostruendo la mia vista da zero e quando ho diffuso i 2 file dello storyboard l'unica differenza era che nella versione con gli avvertimenti mancava la linea <rect key="frame" x="0.0" y="0.0" width="600" height="110"/>nella sua definizione, portandomi a credere che si tratti di un bug IB . Almeno il mio lo era comunque.
Ell Neal,

Risposte:


301

Prova a ridurre la priorità del tuo _collapsedtextHeightConstrainta 999. In questo modo il UIView-Encapsulated-Layout-Heightvincolo fornito dal sistema ha sempre la precedenza.

Si basa su ciò in cui ritorni -tableView:heightForRowAtIndexPath:. Assicurarsi di restituire il valore corretto e il proprio vincolo e quello generato dovrebbero essere gli stessi. La priorità inferiore per il proprio vincolo è necessaria solo temporaneamente per prevenire conflitti mentre le animazioni di compressione / espansione sono in volo.


74
Ciò raggiungerebbe esattamente l'opposto di quello che voglio. UIView-Encapsulated-Layout-Height è errato: appartiene al layout precedente.
Rog,

7
Il UIView-Encapsulated-Layout-Heightvincolo viene aggiunto da UITableView una volta determinate le altezze. Calcolo l'altezza in base a systemLayoutSizeFittingSizecontentView. Qui UIView-Encapsulated-Layout-Heightnon importa. Quindi, tableView imposta contentSize in modo esplicito sul valore restituito da heightForRowAtIndexPath:. In questo caso è corretto ridurre la priorità dei nostri vincoli personalizzati poiché il vincolo tableView deve avere la precedenza dopo il calcolo di rowHeights.
Ortwin Gentz,

8
@OrtwinGentz: Ancora non si capisce che è corretto ridurre la priorità dei nostri vincoli personalizzati perché il vincolo tableView deve avere la precedenza dopo il calcolo di rowHeights . La cosa è UIView-Encapsulated-Layout-Heightsbagliata se non abbasso la priorità ...
testando il

38
Ritengo che evitare il conflitto non risolva il problema reale, sebbene siccome sembra ancora un bug di Apple, penso che sia probabilmente la cosa giusta da fare. Soprattutto alla luce del fatto che Apple ricalcola questo vincolo e che tutto viene disposto correttamente dopo la stampa dell'errore.
Rog

9
-1 per questa risposta poiché presuppone che il vincolo aggiunto sia corretto. L'aggiunta UIView-Encapsulated-Layout-Widthnel mio caso è semplicemente sbagliata, ma sembra essere preferita rispetto ai miei vincoli espliciti in fase di esecuzione.
ray

68

Ho uno scenario simile: una vista tabella con una cella di riga, in cui ci sono alcune linee di oggetti UILabel. Sto usando iOS 8 e autolayout.

Quando ho ruotato ho ottenuto l'altezza della riga calcolata dal sistema sbagliato (43,5 è molto inferiore all'altezza effettiva). Sembra:

"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"

Non è solo un avvertimento. Il layout della mia cella di visualizzazione tabella è terribile: tutto il testo si sovrappone su una riga di testo.

Mi sorprende che la seguente riga "risolva" magicamente il mio problema (il completamento automatico non si lamenta di nulla e ottengo ciò che mi aspetto sullo schermo):

myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works

con o senza questa linea:

myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem

8
Finalmente! Questa è una risposta corretta Nella sessione del WWDC, è stato menzionato, se utilizzerai il dimensionamento automatico dell'altezza della riga, dovrai impostare un valore di RowHeight stimato o succederanno cose brutte. (Sì, sono successe cose davvero brutte alla Apple)
Abdalrahman Shatou,

50
FWIW, l'aggiunta del preventivo non ha fatto alcuna differenza per me.
Benjohn,

3
Eh. Sono tornato di nuovo a questa stessa risposta e sono andato a implementarla con gioia e speranza. Ancora una volta, non ha fatto alcuna differenza per me :-)
Benjohn,

3
Stime restituite da tableView: stimatoHeightForRowAtIndexPath: DEVE essere almeno grande quanto la cella. Altrimenti, l'altezza calcolata della tabella sarà inferiore all'altezza effettiva e la tabella potrebbe scorrere verso l'alto (ad es. Dopo che lo svolgimento segue il ritorno alla tabella). UITableViewAutomaticDimension non deve essere utilizzato.
Matt,

1
Si noti che più basso è il estimatedRowHeightpiù spesso cellForRowAtIndexPathverrà chiamato inizialmente. altezza vista tabella divisa per i tempi stimati di RowHeight, per l'esattezza. Su un iPad Pro da 12 pollici, questo può potenzialmente essere un numero in migliaia e martellare l'origine dati e può causare un ritardo significativo.
Mojo66,

32

Sono stato in grado di far scomparire l'avviso specificando una priorità su uno dei valori nel vincolo che i messaggi di avviso dicono che doveva rompere (di seguito "Will attempt to recover by breaking constraint"). Sembra che fino a quando imposto la priorità su qualcosa di più grande 49, l'avvertimento scompare.

Per me questo significava cambiare il mio vincolo che l'avvertimento diceva che tentava di rompere:

@"V:|[contentLabel]-[quoteeLabel]|"

per:

@"V:|-0@500-[contentLabel]-[quoteeLabel]|"

In effetti, posso aggiungere una priorità a qualsiasi degli elementi di quel vincolo e funzionerà. Non sembra importare quale. Le mie celle raggiungono l'altezza corretta e l'avviso non viene visualizzato. Roger, per esempio, prova ad aggiungere @500subito dopo il 388vincolo del valore di altezza (ad es 388@500.).

Non sono del tutto sicuro del perché funzioni, ma ho fatto un po 'di indagine. Nel enum NSLayoutPriority , sembra che il NSLayoutPriorityFittingSizeCompressionlivello di priorità è 50. La documentazione per quel livello di priorità dice:

Quando si invia un messaggio fittingSize a una vista, viene calcolata la dimensione più piccola sufficientemente grande per il contenuto della vista. Questo è il livello di priorità con cui la vista vuole essere il più piccola possibile in quel calcolo. È abbastanza basso. In genere non è appropriato imporre esattamente questa priorità. Vuoi essere più alto o più basso.

La documentazione per il fittingSizemessaggio di riferimento recita:

La dimensione minima della vista che soddisfa i vincoli che detiene. (sola lettura)

AppKit imposta questa proprietà sulla migliore dimensione disponibile per la vista, considerando tutti i vincoli che essa detiene e le sue visualizzazioni secondarie e soddisfando una preferenza per rendere la vista il più piccola possibile. I valori di dimensione in questa proprietà non sono mai negativi.

Non ho scavato oltre ma sembra logico che ciò abbia a che fare con il problema.


Grazie Jeff. Mi sembra ancora un insetto. Apple non ha risposto al rdar però :-(
Rog

13

Il 99,9% delle volte, durante l'utilizzo di celle o intestazioni personalizzate, si UITableViewsverificano tutti i conflitti quando la tabella viene caricata per la prima volta. Una volta caricato, di solito non vedrai più il conflitto.

Ciò accade perché la maggior parte degli sviluppatori utilizza in genere un'altezza fissa o un vincolo di ancoraggio di qualche tipo per impaginare un elemento nella cella / intestazione. Il conflitto si verifica perché quando il UITableViewprimo viene caricato / disposto, imposta l'altezza delle sue celle su 0. Ciò ovviamente è in conflitto con i propri vincoli. Per risolvere questo problema, è sufficiente impostare eventuali vincoli di altezza fissi su una priorità inferiore ( .defaultHigh). Leggere attentamente il messaggio della console e vedere quale vincolo il sistema di layout ha deciso di interrompere. Di solito questo è quello che deve cambiare la sua priorità. Puoi modificare la priorità in questo modo:

let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
    companyNameTopConstraint.priority = .defaultHigh

NSLayoutConstraint.activate([
            companyNameTopConstraint,
           the rest of your constraints here
            ])

1
Bella spiegazione
Glenn,

12

Sono stato in grado di risolvere questo errore rimuovendo una spuria cell.layoutIfNeeded()che ho avuto nella mia tableView's cellForRowAtmetodo.


1
Sì, questo ha risolto il problema anche per me. Stavo facendo dei contrasti nella disposizione del codice, quindi inizialmente pensavo che potessi perdere qualcosa. Grazie
John,

1
La stessa cosa! Grazie!
Andrey Chernukha,

7

Invece di informare la vista tabella per aggiornarne i vincoli, prova a ricaricare la cella:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView reloadRowsAtIndexPaths:@[[tableView indexPathForCell:_briefCell]] withRowAnimation:UITableViewRowAnimationNone];

[tableView endUpdates];

UIView-Encapsulated-Layout-Height è probabilmente l'altezza calcolata dalla vista tabella per la cella durante il caricamento iniziale, in base ai vincoli della cella in quel momento.


Avrei dovuto dichiarare nella mia domanda, ho provato questo e non funziona. Sono d'accordo sulla tua ipotesi su UIView-Encapsulated-Layout-Height
Rog

6
Avere comunque la generosità almeno per aver inviato una risposta. Sembra che SO farà semplicemente evaporare altrimenti.
Rog,

6

Un'altra possibilità:

Se si utilizza il layout automatico per calcolare l'altezza della cella (l'altezza di contentView, la maggior parte delle volte come di seguito) e se si dispone del separatore uitableview, è necessario aggiungere l'altezza del separatore, al fine di restituire l'altezza della cella. Una volta che hai raggiunto l'altezza corretta, non avrai quell'avviso di autolayout.

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
   [sizingCell setNeedsLayout];
   [sizingCell layoutIfNeeded];
   CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
   return size.height; // should + 1 here if my uitableviewseparatorstyle is not none

}

Ciò mi ha aiutato in un caso in cui ho avuto ambiguità dell'altezza del layout in un layout di cella piuttosto complesso solo in alcuni simulatori (iPad 6 Plus). Mi sembra che a causa di alcuni errori di arrotondamento interno il contenuto sia un po 'schiacciato e se i vincoli non sono preparati per essere schiacciati di quanto io abbia ottenuto ambiguità. Così, invece di tornare UITableViewAutomaticDimensionin heightForRowAtIndexPathI tornare [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
Leo

Intendo ovviamente[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
Leo,

incredibilmente; rimuovere il separatore è ciò che ha comportato il mio tavolo, quindi grazie.
royalmurder,

5

Come menzionato dal commento di Jesse in questione, questo funziona per me:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

Cordiali saluti, questo problema non si verifica in iOS 10.


2
In Swift 4.2: self.contentView.autoresizingMask = [.f flexibleHeight]
airowe

4

Ho riscontrato questo errore durante l'utilizzo di UITableViewAutomaticDimension e la modifica di un vincolo di altezza in una vista all'interno della cella.

Alla fine ho capito che era dovuto al fatto che il valore della costante del vincolo non veniva arrotondato al numero intero più vicino.

let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!

2
Questo è stato il commento che mi ha fatto capire il mio problema. Ho una cella di immagine che si carica in modo dinamico (cresce e si restringe) e una vista tabella con stimaRowHeight = 50 e rowHeight = UITableViewAutomaticDimension. Stavo ancora rompendo i vincoli anche se la vista da tavolo era all'altezza corretta. Si scopre che i separatori erano alti 0,3333 ed era quello che stava rompendo il mio vincolo di dimensione dell'immagine nella cella. Dopo aver spento i separatori tutto andava bene. Grazie Che per avermi dato cosa cercare.
Migs647,

1
In questo caso, creare un vincolo aggiuntivo, ad esempio bottomMargin> = view.bottomMargin+1@900. AutoLayout tenta di accogliere 1 punto in più, ridimensiona la cella, si confonde a causa dell'altezza del separatore, cerca di rompere / rilassare alcuni vincoli, trova quello a 900 e lo scarta. Ottieni il layout desiderato senza avvisi.
Anton,

salva la mia giornata. poche ore comunque
Anton Tropashko

1

Dimensionare la vista del testo per adattarla al suo contenuto e aggiornare la costante di vincolo di altezza all'altezza risultante, risolto il UIView-Encapsulated-Layout-Heightconflitto di vincolo per me, ad esempio:

[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;

Dove l'hai fatto? In layoutSubviews?
stuckj,

1

Dopo aver trascorso alcune ore a grattarmi la testa con questo bug, ho finalmente trovato una soluzione che ha funzionato per me. il mio problema principale era che avevo più pennini registrati per diversi tipi di celle ma a un tipo specifico di cella era consentito avere dimensioni diverse (non tutte le istanze di quella cella avranno le stesse dimensioni). quindi il problema si presentava quando la vista da tavolo stava provando a dequequare una cella di quel tipo e aveva un'altezza diversa. L'ho risolto impostando

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; self.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

ogni volta che la cella aveva i suoi dati per calcolare le sue dimensioni. Immagino che possa essere dentro

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

qualcosa di simile a

cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; cell.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

Spero che questo ti aiuti!


0

TableView ottiene l'altezza per la cella su indexPath dal delegato. quindi ottenere cella da cellForRowAtIndexPath:

top (10@1000)
    cell
bottom (0@1000)

se cell.contentView.height: 0 // <-> (UIView-Encapsulated-Layout-Height: 0 @ 1000) top (10 @ 1000) in conflitto con (UIView-Encapsulated-Layout-Height: 0 @ 1000),

perché le loro priorità sono pari a 1000. Dobbiamo impostare la massima priorità sotto UIView-Encapsulated-Layout-Heightla priorità di.


0

Stavo ricevendo un messaggio come questo:

Impossibile soddisfare contemporaneamente i vincoli ...
...
...
...
NSLayoutConstraint: 0x7fe74bdf7e50 'UIView-Encapsulated-Layout-Height' V: [UITableViewCellContentView: 0x7fe75330c5c0 (21.5)]
...
... Tenterà
di recuperare entro vincolo di rottura NSLayoutConstraint: 0x7fe0f9b200c0 UITableViewCellContentView: 0x7fe0f9b1e090.bottomMargin == UILabel: 0x7fe0f9b1e970.bottom

Sto usando un'abitudine UITableViewCellcon UITableViewAutomaticDimensionper l'altezza. E ho anche implementato il estimatedHeightForRowAtIndex:metodo.

Il vincolo che mi stava dando problemi sembrava qualcosa del genere

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[title]-|" options:0 metrics:nil views:views];

La modifica del vincolo in questo risolverà il problema, ma come un'altra risposta ho ritenuto che ciò non fosse corretto, in quanto riduce la priorità di un vincolo che desidero essere richiesto:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6@999-[title]-6@999-|" options:0 metrics:nil views:views];

Tuttavia, ciò che ho notato è che se rimuovo semplicemente la priorità, anche questo funziona e non ottengo i registri dei vincoli di rottura:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6-[title]-6-|" options:0 metrics:nil views:views];

Questo è un po 'un mistero su quale sia la differenza tra |-6-[title]-6-|e |-[title-|. Ma specificare le dimensioni non è un problema per me e si elimina i registri e non ho bisogno di ridurre la priorità dei miei vincoli richiesti.


0

Impostare questo view.translatesAutoresizingMaskIntoConstraints = NO;dovrebbe risolvere questo problema.


Questo ha funzionato perfettamente per me, anche se non era la soluzione perfetta.
Enkha,

0

Ho avuto un problema simile con una cella di visualizzazione raccolta.

L'ho risolto abbassando la priorità del vincolo finale che era collegato al fondo della cella (l'ultimo nella catena dall'alto verso il basso della vista - questo è in definitiva ciò che determina la sua altezza) a 999.

L'altezza della cella era corretta e gli avvertimenti scomparvero.

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.