Qual é a relação entre setNeedsLayout, layoutIfNeeded e layoutSubviews do UIView?
105
Alguém pode dar uma explicação definitiva sobre a relação entre UIView'ssetNeedsLayout, layoutIfNeedede layoutSubviewsmétodos? E um exemplo de implementação onde todos os três seriam usados. Obrigado.
O que me deixa confuso é que, se eu enviar uma setNeedsLayoutmensagem para o meu modo de exibição personalizado, a próxima coisa que ele invoca após esse método é layoutSubviewspular imediatamente layoutIfNeeded. Pelos documentos, eu esperaria que o fluxo fosse setNeedsLayout> causas layoutIfNeededa serem chamadas> causas layoutSubviewsa serem chamadas.
Ainda estou tentando descobrir isso sozinho, então aceite isso com algum ceticismo e me perdoe se contém erros.
setNeedsLayouté fácil: ele apenas define um sinalizador em algum lugar do UIView que o marca como necessitando de layout. Isso forçará layoutSubviewsa chamada na visualização antes que o próximo redesenho aconteça. Observe que, em muitos casos, você não precisa chamar isso explicitamente, por causa da autoresizesSubviewspropriedade. Se estiver definido (o que é por padrão), qualquer alteração no quadro de uma visão fará com que a visão exiba suas subvisualizações.
layoutSubviewsé o método pelo qual você faz todas as coisas interessantes. É o equivalente drawRecta layout, se você quiser. Um exemplo trivial pode ser:
-(void)layoutSubviews {// Child's frame is always equal to our bounds inset by 8pxself.subview1.frame =CGRectInset(self.bounds,8.0,8.0);// It seems likely that this is incorrect:// [self.subview1 layoutSubviews];// ... and this is correct:[self.subview1 setNeedsLayout];// but I don't claim to know definitively.}
AFAIK layoutIfNeededgeralmente não deve ser sobrescrito em sua subclasse. É um método que você deve chamar quando quiser que uma visualização seja apresentada agora . A implementação da Apple pode ser mais ou menos assim:
Obrigado - fico feliz que alguém finalmente respondeu isso. Nesse ínterim, também tive que me aprofundar em setNeedsDisplay - que faz com que drawRect seja chamado - e contentMode. Apenas contentMode = UIViewContentModeRedraw fará com que setNeedsDisplay seja chamado quando os limites de uma visualização forem alterados. As outras opções de contentMode apenas fazem com que a visualização seja dimensionada, deslocada em alguma quantidade ou alinhada a uma borda. Meu UITableViewCell personalizado não queria redesenhar as mudanças de orientação, ele apenas escalaria, até que eu definisse contentMode como UIViewContentModeRedraw.
Tarfa
Acho que talvez o [self.subview1 layoutSubviews] em seu código deva ser substituído por [self.subview1 setNeedsLayout]. Já que layoutSubviews deve ser sobrescrito, mas não deve ser chamado de ou para outra visão. A estrutura determina quando chamá-la (agrupando várias solicitações de layout em uma chamada de eficiência) ou indiretamente chamando layoutIfNeeded em algum lugar na hierarquia de visualização.
Tarfa
Correção para o meu comentário acima: "... Visto que layoutSubviews deve ser substituído ou chamado por si mesmo, mas não deve ser chamado de ou para outra visualização ..."
Tarfa
Eu realmente não tenho certeza sobre [subview1 layoutSubviews]. Pode muito bem ser que drawRectnão deva ligar diretamente. Mas não tenho certeza se a chamada setNeedsLayoutdurante a fase de layout fará com que a visualização seja exibida durante a mesma fase de layout ou se atrasar até a próxima. Seria bom ver uma resposta de alguém que realmente entende como tudo isso funciona ...
n8gray
34
Gostaria de acrescentar a resposta do n8gray que, em alguns casos, você precisará ligar setNeedsLayoutseguido por layoutIfNeeded.
Digamos, por exemplo, que você escreveu uma visualização customizada estendendo UIView, na qual o posicionamento de subvisualizações é complexo e não pode ser feito com autoresizingMask ou iOS6 AutoLayout. O posicionamento personalizado pode ser feito substituindo layoutSubviews.
Como exemplo, digamos que você tenha uma visualização personalizada que possui uma contentViewpropriedade e uma edgeInsetspropriedade que permite definir as margens ao redor de contentView. layoutSubviewsficaria assim:
Se você deseja poder animar a mudança de quadro sempre que alterar a edgeInsetspropriedade, você precisa substituir o configurador da edgeInsetsseguinte maneira e chamar setNeedsLayoutseguido por layoutIfNeeded:
-(void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
_edgeInsets = edgeInsets;[self setNeedsLayout];//Indicates that the view needs to be laid out //at next update or at next call of layoutIfNeeded, //whichever comes first [self layoutIfNeeded];//Calls layoutSubviews if flag is set}
Dessa forma, se você fizer o seguinte, se alterar a propriedade edgeInsets dentro de um bloco de animação, a mudança de quadro de contentView será animada.
Se você não adicionar a chamada para layoutIfNeeded no método setEdgeInsets, a animação não funcionará porque o layoutSubviews será chamado no próximo ciclo de atualização, o que equivale a chamá-lo fora do bloco de animação.
Se você chamar layoutIfNeeded no método setEdgeInsets, nada acontecerá, pois o sinalizador setNeedsLayout não está definido.
[subview1 layoutSubviews]
. Pode muito bem ser quedrawRect
não deva ligar diretamente. Mas não tenho certeza se a chamadasetNeedsLayout
durante a fase de layout fará com que a visualização seja exibida durante a mesma fase de layout ou se atrasar até a próxima. Seria bom ver uma resposta de alguém que realmente entende como tudo isso funciona ...Gostaria de acrescentar a resposta do n8gray que, em alguns casos, você precisará ligar
setNeedsLayout
seguido porlayoutIfNeeded
.Digamos, por exemplo, que você escreveu uma visualização customizada estendendo UIView, na qual o posicionamento de subvisualizações é complexo e não pode ser feito com autoresizingMask ou iOS6 AutoLayout. O posicionamento personalizado pode ser feito substituindo
layoutSubviews
.Como exemplo, digamos que você tenha uma visualização personalizada que possui uma
contentView
propriedade e umaedgeInsets
propriedade que permite definir as margens ao redor de contentView.layoutSubviews
ficaria assim:Se você deseja poder animar a mudança de quadro sempre que alterar a
edgeInsets
propriedade, você precisa substituir o configurador daedgeInsets
seguinte maneira e chamarsetNeedsLayout
seguido porlayoutIfNeeded
:Dessa forma, se você fizer o seguinte, se alterar a propriedade edgeInsets dentro de um bloco de animação, a mudança de quadro de contentView será animada.
Se você não adicionar a chamada para layoutIfNeeded no método setEdgeInsets, a animação não funcionará porque o layoutSubviews será chamado no próximo ciclo de atualização, o que equivale a chamá-lo fora do bloco de animação.
Se você chamar layoutIfNeeded no método setEdgeInsets, nada acontecerá, pois o sinalizador setNeedsLayout não está definido.
fonte
[self layoutIfNeeded]
dentro do bloco de animação depois de definir as inserções de borda.