setNeedsLayout vs. setNeedsUpdateConstraints and layoutIfNeeded vs updateConstraintsIfNeeded


227

So che la catena di layout automatico consiste sostanzialmente in 3 processi diversi.

  1. vincoli di aggiornamento
  2. viste di layout (qui è dove otteniamo il calcolo dei frame)
  3. Schermo

Ciò che non è del tutto chiaro per me è la differenza interiore tra -setNeedsLayoute -setNeedsUpdateConstraints. Da Apple Docs:

setNeedsLayout

Chiama questo metodo sul thread principale dell'applicazione quando desideri regolare il layout delle viste secondarie di una vista. Questo metodo prende nota della richiesta e restituisce immediatamente. Poiché questo metodo non impone un aggiornamento immediato, ma attende invece il ciclo di aggiornamento successivo, è possibile utilizzarlo per invalidare il layout di più viste prima che una di tali viste venga aggiornata. Questo comportamento consente di consolidare tutti gli aggiornamenti del layout in un ciclo di aggiornamento, che di solito è migliore per le prestazioni.

setNeedsUpdateConstraints

Quando una proprietà della vista personalizzata cambia in modo tale da influire sui vincoli, è possibile chiamare questo metodo per indicare che i vincoli dovranno essere aggiornati in futuro. Il sistema chiamerà quindi updateConstraints come parte del suo normale passaggio di layout. L'aggiornamento dei vincoli in una sola volta, appena prima che siano necessari, garantisce che non sia necessario ricalcolare inutilmente i vincoli quando vengono apportate più modifiche alla vista tra passaggi del layout.

Quando voglio animare una vista dopo aver modificato un vincolo e animare le modifiche che di solito chiamo per esempio:

[UIView animateWithDuration:1.0f delay:0.0f usingSpringWithDamping:0.5f initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        [self.modifConstrView setNeedsUpdateConstraints];
        [self.modifConstrView layoutIfNeeded];
    } completion:NULL];

Ho scoperto che se uso -setNeedsLayoutinvece di -setNeedsUpdateConstraintstutto funziona come previsto, ma se cambio -layoutIfNeededcon -updateConstraintsIfNeeded, l'animazione non avverrà.
Ho cercato di trarre le mie conclusioni:

  • -updateConstraintsIfNeeded aggiorna solo i vincoli ma non impone al layout di entrare nel processo, quindi i frame originali vengono comunque conservati
  • -setNeedsLayoutchiama anche -updateContraintsmetodo

Quindi, quando va bene usare l'uno invece dell'altro? e riguardo ai metodi di layout, devo chiamarli sulla vista che ha una modifica in un vincolo o sulla vista padre?


27
Non capisco il downvoting delle persone ... davvero. Quindi dovresti fare qualcosa al riguardo, come chiedere un motivo come obbligatorio o sono totalmente inutili
Andrea

7
Forse hanno solo bisogno di ottenere il badge Critic (Primo voto discendente)
fujianjin6471

1
Consiglio vivamente di vedere qui . La risposta è più di una soluzione a un problema reale. Vedi anche questo video
Honey,

Risposte:


258

Le tue conclusioni sono giuste. Lo schema di base è:

  • setNeedsUpdateConstraintssi assicura una futura chiamata alle updateConstraintsIfNeededchiamate updateConstraints.
  • setNeedsLayoutsi assicura una futura chiamata alle layoutIfNeededchiamate layoutSubviews.

Quando layoutSubviewsviene chiamato, chiama anche updateConstraintsIfNeeded, quindi nella mia esperienza raramente è necessario chiamarlo manualmente. In realtà, non l'ho mai chiamato, tranne quando si eseguono debug di layout.

Anche l'aggiornamento dei vincoli usando setNeedsUpdateConstraintsè piuttosto raro, objc.io - un must leggere sui ritardi automatici - dice :

Se in seguito qualcosa cambia che invalida uno dei tuoi vincoli, dovresti rimuoverlo immediatamente e chiamare setNeedsUpdateConstraints. In realtà, questo è l'unico caso in cui dovresti attivare un passaggio di aggiornamento del vincolo.

Inoltre, nella mia esperienza, non ho mai dovuto invalidare i vincoli e non impostare la setNeedsLayoutriga successiva del codice, poiché i nuovi vincoli richiedono praticamente un nuovo layout.

Le regole empiriche sono:

  • Se hai manipolato direttamente i vincoli, chiama setNeedsLayout.
  • Se hai modificato alcune condizioni (come offset o smth) che avrebbero cambiato i vincoli nel tuo updateConstraintsmetodo ignorato (un modo consigliato per cambiare i vincoli, tra l'altro), chiama setNeedsUpdateConstraintse la maggior parte delle volte, setNeedsLayoutsuccessivamente.
  • Se hai bisogno di una delle azioni sopra descritte per avere un effetto immediato, ad esempio quando hai bisogno di imparare la nuova altezza del telaio dopo un passaggio di layout, aggiungilo a layoutIfNeeded.

Inoltre, nel tuo codice di animazione, credo che setNeedsUpdateConstraintsnon sia necessario, poiché i vincoli vengono aggiornati prima dell'animazione manualmente e l'animazione ridefinisce la vista solo in base alle differenze tra il vecchio e il nuovo.


@coverback, quindi objc.io dice "Se in seguito qualcosa cambia che invalida uno dei tuoi vincoli, dovresti rimuovere immediatamente il vincolo e chiamare setNeedsUpdateConstraints. In realtà, è l'unico caso in cui dovresti attivare un passaggio di aggiornamento del vincolo." E poi nel blocco Animazione si dice che quando rimuovo, aggiungo o cambio il vincolo.contant devo chiamare setNeedsLayout. Qual è la differenza? Mi sento davvero stupido :(
pash3r,

3
@ pash3r La differenza sta aggiornando la costante non si qualifica come "invalidazione". L'invalidazione è quando non è più rilevante, come deve essere collegata a un'altra vista o rimossa completamente. Costante avrebbe semplicemente posto uno sguardo più vicino o più lontano, o avrebbe cambiato la sua dimensione, quindi la necessità di setNeedsLayout.
coverback

@coverback si setNeedsLayoutassicura layoutSubviewsche verrà chiamato nel prossimo ciclo di aggiornamento, ma forse questo non ha nulla a che fare con layoutIfNeeded?
fujianjin6471

2
@coverback Se si manipolano direttamente i vincoli, layoutSubviewsverranno chiamati automaticamente, non è necessario chiamaresetNeedsLayout
fujianjin6471

Sì, la manipolazione diretta delle proprietà di un vincolo si attiverà layoutSubviews, quindi non è necessario eseguirla manualmente. Tuttavia, layoutIfNeededdevi chiamare se hai bisogno che le modifiche abbiano effetto immediato invece del prossimo ciclo di layout
Charlie Martin

89

La risposta per coverback è abbastanza corretta. Tuttavia, vorrei aggiungere alcuni dettagli aggiuntivi.

Di seguito è riportato il diagramma di un tipico ciclo UIView che spiega altri comportamenti:

Ciclo di vita di UIView

  1. Ho scoperto che se uso -setNeedsLayoutinvece di -setNeedsUpdateConstraintstutto funziona come previsto, ma se cambio -layoutIfNeededcon -updateConstraintsIfNeeded, l'animazione non avverrà.

updateConstraintsin genere non fa nulla. Risolve solo i vincoli che non li applica fino a quando non layoutSubviewsviene chiamato. Quindi l'animazione richiede una chiamata a layoutSubviews.

  1. setNeedsLayout chiama anche il metodo -updateContraints

No questo non è necessario. Se i tuoi vincoli non sono stati modificati, UIView salterà la chiamata a updateConstraints. È necessario chiamare esplicitamente setNeedsUpdateConstraintper modificare i vincoli nel processo.

Per chiamare updateConstraintsè necessario effettuare le seguenti operazioni:

[view setNeedsUpdateConstraints];
[view setNeedsLayout]; 
[view layoutIfNeeded];

Grazie, questo ha risolto il mio problema. Avevo una UIWindow senza UIView principale a cui venivano aggiunti dei vincoli temporanei quando si chiamava LayoutIfNeeded () prima di un'animazione. L'aggiunta di un wrapper di subview alla UIWindow e la chiamata a questi tre metodi ha risolto il mio problema.
masterwok,

Non penso che chiamare layoutIfNeeded subito dopo setNeedsLayout sia corretto. Perché i metodi fanno lo stesso nonostante il fatto che uno stia facendo ridisegnare immediatamente il layout e il secondo in un successivo ciclo di aggiornamento.
fillky
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.