setNeedsLayout vs. setNeedsUpdateConstraints e layoutIfNeeded vs updateConstraintsIfNeeded

227

Eu sei que a cadeia de layout automático consiste basicamente em 3 processos diferentes.

  1. atualizando restrições
  2. visualizações de layout (aqui é onde obtemos o cálculo de quadros)
  3. exibição

O que não está totalmente claro para mim é a diferença interior entre -setNeedsLayoute -setNeedsUpdateConstraints. Do Apple Docs:

setNeedsLayout

Chame esse método no thread principal do seu aplicativo quando desejar ajustar o layout das subvisões de uma exibição. Este método anota a solicitação e retorna imediatamente. Como esse método não força uma atualização imediata, mas aguarda o próximo ciclo de atualização, você pode usá-lo para invalidar o layout de várias visualizações antes que qualquer uma dessas visualizações seja atualizada. Esse comportamento permite consolidar todas as atualizações de layout em um ciclo de atualização, o que geralmente é melhor para o desempenho.

setNeedsUpdateConstraints

Quando uma propriedade de sua visualização personalizada é alterada de maneira a afetar as restrições, você pode chamar esse método para indicar que as restrições precisam ser atualizadas em algum momento no futuro. O sistema chamará updateConstraints como parte de seu passe de layout normal. A atualização de restrições de uma só vez antes de serem necessárias garante que você não recalcule desnecessariamente as restrições quando várias alterações são feitas na sua visualização entre as passagens de layout.

Quando eu quero animar uma exibição depois de modificar uma restrição e animar as alterações que eu costumo chamar, por exemplo:

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

Eu descobri que se eu usar -setNeedsLayoutem vez de -setNeedsUpdateConstraintstudo funcionar como esperado, mas se eu mudar -layoutIfNeededcom -updateConstraintsIfNeeded, a animação não vai acontecer.
Eu tentei fazer minha própria conclusão:

  • -updateConstraintsIfNeeded atualiza apenas as restrições, mas não força o layout a entrar no processo; portanto, os quadros originais ainda são preservados
  • -setNeedsLayoutchama também -updateContraintsmétodo

Então, quando é bom usar um em vez do outro? e sobre os métodos de layout, preciso chamá-los na exibição que possui uma alteração em uma restrição ou na exibição pai?

Andrea
fonte
27
Eu não entendo as pessoas que votam mal ... realmente. Portanto, você deve fazer algo sobre isso, como pedir a um motivo como obrigatório ou eles são totalmente inútil
Andrea
7
Talvez eles só precisam de obter o emblema Critic (Primeira baixo votação)
fujianjin6471
1
Eu recomendo que você veja aqui . A resposta é mais uma solução para um problema real. Veja também este vídeo
Honey

Respostas:

258

Suas conclusões estão certas. O esquema básico é:

  • setNeedsUpdateConstraintsgarante uma futura chamada para updateConstraintsIfNeededchamadas updateConstraints.
  • setNeedsLayoutgarante uma futura chamada para layoutIfNeededchamadas layoutSubviews.

Quando layoutSubviewsé chamado, também chama updateConstraintsIfNeeded, portanto, raramente é necessário chamá-lo manualmente. Na verdade, nunca o chamei, exceto ao depurar layouts.

A atualização de restrições usando também setNeedsUpdateConstraintsé bastante rara, objc.io - é necessário ler sobre os layouts automáticos - diz :

Se algo mudar posteriormente que invalide uma de suas restrições, remova a restrição imediatamente e chame setNeedsUpdateConstraints. Na verdade, esse é o único caso em que você deve acionar um passe de atualização de restrição.

Além disso, na minha experiência, nunca tive que invalidar restrições e não definir setNeedsLayoutna próxima linha do código, porque novas restrições praticamente exigem um novo layout.

As regras práticas são:

  • Se você manipulou restrições diretamente, ligue setNeedsLayout.
  • Se você alterou algumas condições (como compensações ou smth) que iria mudar restrições na sua substituído updateConstraintsmétodo (a maneira recomendada para restrições de mudança, btw), chamada setNeedsUpdateConstraints, e na maioria das vezes, setNeedsLayoutdepois disso.
  • Se você precisar que qualquer uma das ações acima tenha efeito imediato - por exemplo, quando você precisar aprender uma nova altura de quadro após uma aprovação de layout - acrescente-a a layoutIfNeeded.

Além disso, no seu código de animação, acredito que não setNeedsUpdateConstraintsé necessário, pois as restrições são atualizadas manualmente antes da animação, e a animação apenas re-expõe a exibição com base nas diferenças entre as antigas e as novas.

coverback
fonte
@coverback, então objc.io diz "Se algo mudar posteriormente que invalide uma de suas restrições, você deverá remover a restrição imediatamente e chamar setNeedsUpdateConstraints. Na verdade, esse é o único caso em que você deverá acionar um passe de atualização de restrição". E então, no bloco Animação, ele diz que, quando removo, adiciono ou altero constraint.contant, devo chamar setNeedsLayout. Qual é a diferença? Eu me sinto muito estúpido :(
pash3r
3
@ pash3r A diferença na atualização constante não se qualifica como "invalidação". A invalidação ocorre quando não é mais relevante, como deve ser anexada a outra visualização ou removida completamente. Constant apenas colocaria uma visão mais próxima ou mais longe, ou mudaria de tamanho, daí a necessidade setNeedsLayout.
coverback
O @coverback setNeedsLayoutgarante que layoutSubviewsserá chamado no próximo ciclo de atualização, mas talvez isso não tenha nada a ver com isso layoutIfNeeded?
Fujianjin6471
2
@coverback Se você manipular restrições directamente, layoutSubviewsserá chamado automaticamente, sem necessidade de chamarsetNeedsLayout
fujianjin6471
Sim, a manipulação direta das propriedades de uma restrição será acionada layoutSubviews, portanto, não é necessário fazê-lo manualmente. Você, no entanto, tem que chamar layoutIfNeededse precisar que as alterações tenham efeito imediato, em vez do próximo ciclo de layout
Charlie Martin
89

A resposta do coverback é bastante correta. No entanto, gostaria de adicionar alguns detalhes adicionais.

Abaixo está o diagrama de um ciclo UIView típico que explica outros comportamentos:

Ciclo de vida do UIView

  1. Eu descobri que se eu usar -setNeedsLayoutem vez de -setNeedsUpdateConstraintstudo funcionar como esperado, mas se eu mudar -layoutIfNeededcom -updateConstraintsIfNeeded, a animação não vai acontecer.

updateConstraintsnormalmente não faz nada. Ele apenas resolve restrições que não as aplica até que layoutSubviewsseja chamado. Portanto, a animação requer uma chamada para layoutSubviews.

  1. setNeedsLayout também chama o método -updateContraints

Não, isso não é necessário. Se suas restrições não foram modificadas, o UIView passará a chamada para updateConstraints. Você precisa chamar explicitamente setNeedsUpdateConstraintpara modificar restrições no processo.

Para ligar, updateConstraintsvocê precisa fazer o seguinte:

[view setNeedsUpdateConstraints];
[view setNeedsLayout]; 
[view layoutIfNeeded];
Kunal Balani
fonte
Obrigado, isso resolveu meu problema. Eu tinha um UIWindow sem pai UIView que tinha restrições temporárias sendo adicionadas ao chamar LayoutIfNeeded () antes de uma animação. Adicionar um invólucro de subview à UIWindow e chamar esses três métodos nele corrigiu meu problema.
masterwok
Não acho que chamar layoutIfNeeded logo após o setNeedsLayout esteja correto. Porque os métodos fazem o mesmo, apesar de um deles estar causando o redesenho do layout imediatamente e o segundo no próximo ciclo de atualização.
Fillky