Como posso obter a largura e altura atuais de uma visualização ao usar restrições de layout automático?

108

Não estou falando sobre a propriedade frame, porque a partir dela você só pode obter o tamanho da visualização no xib. Estou falando sobre quando a visualização é redimensionada por causa de suas restrições (talvez após uma rotação ou em resposta a um evento). Existe uma maneira de obter sua largura e altura atuais?

Eu tentei iterar por meio de suas restrições procurando por restrições de largura e altura, mas isso não é muito claro e falha quando há restrições intrínsecas (já que não consigo diferenciar entre as duas). Além disso, isso só funciona se eles realmente tiverem restrições de largura e altura, o que não acontece se dependerem de outras restrições para redimensionar.

Por que isso é tão difícil para mim. ARG!

yuf
fonte
Eu teria pensado que os limites da visualização em qualquer momento específico mantinham sua largura e altura atuais. Isso é o que é ajustado durante o processo de layout.
Phillip Mills
Hmm, nunca pensei em verificar os limites. Eu vou fazer isso agora.
yuf

Respostas:

246

A resposta é [view layoutIfNeeded].

Aqui está o porquê:

Você ainda obtém a largura e altura atuais da visualização inspecionando view.bounds.size.widthe view.bounds.size.height(ou o quadro, que é equivalente, a menos que você esteja brincando com view.transform).

Se o que você deseja é a largura e a altura implícitas em suas restrições existentes, a resposta não é inspecionar as restrições manualmente, pois isso exigiria que você reimplementasse toda a lógica de resolução de restrições do sistema de layout automático para interpretá-las restrições. Em vez disso, o que você deve fazer é apenas pedir ao layout automático para atualizar esse layout , para que ele resolva as restrições e atualize o valor de view.bounds com a solução correta e, em seguida, inspecione view.bounds.

Como você pede ao layout automático para atualizar o layout? Ligue [view setNeedsLayout]se quiser que o layout automático atualize o layout na próxima curva do loop de corrida.

No entanto, se você deseja atualizar o layout imediatamente, para que possa acessar imediatamente o novo valor de limites posteriormente em sua função atual ou em outro ponto antes da virada do loop de execução, você precisa chamar [view setNeedsLayout]e [view layoutIfNeeded].

Você fez uma segunda pergunta: "como posso alterar uma restrição de altura / largura se não tenho uma referência a ela diretamente?".

Se você criar a restrição em IB, a melhor solução é criar um IBOutlet em seu controlador de visão ou em sua visão para que você tenha uma referência direta a ele. Se você criou a restrição no código, deve manter uma referência em uma propriedade interna fraca no momento em que a criou. Se outra pessoa criou a restrição, você precisa encontrá-la examinando o exame da propriedade view.constraints na visão e, possivelmente, toda a hierarquia de visão, e implementando a lógica que encontra o NSLayoutConstraint crucial. Este é provavelmente o caminho errado a seguir, uma vez que também requer efetivamente que você determine qual restrição particular determinou o tamanho dos limites, quando não há garantia de que haja uma resposta simples para essa pergunta. O valor final dos limites pode ser a solução para um sistema muito complicado de múltiplas restrições,

algas
fonte
16
Uma resposta excelente e bem explicada também. Me salvou depois de uma ou duas horas puxando meu cabelo.
Ben Kreeger
2
layoutIfNeededé ótimo e tornará o quadro imediatamente disponível. No entanto, também forçará a renderização das restrições em todas as visualizações nas subárvores da visualização na qual é chamada. Se você estiver adicionando visualizações programaticamente, por exemplo, e chamar layoutIfNeededtodas elas em sua rotina recursiva, poderá descobrir que sua hierarquia de visualizações renderiza muito lentamente. (Aprendi isso da maneira mais difícil) Como mencionado na excelente resposta, 'setNeedsLayout` é mais eficiente e disponibilizará o quadro na próxima passagem de layout, o que idealmente acontece em cerca de 1/60 de segundo.
shmim
1
Eu sei que isso é antigo, mas como você chama esse método? Em initWithCoder?
MayNotBe
4
Por que forçar o layout apenas para obter os limites? Isso parece ineficiente e, com uma interface complexa, pode ser potencialmente caro. Em vez disso, acesse os limites mais recentes de viewDidLayoutSubviews ou até mesmo de viewWillLayoutSubviews e deixe o cacau lidar com seu próprio tempo de layout. Defina uma propriedade se precisar acessar o valor ou sinalizador para evitar várias chamadas, se isso for um problema.
smileBot
1
Sim, você só precisa forçar o layout se precisar de acesso ao valor calculado pelo layout automático antes da volta do loop de execução - por exemplo, dentro da extensão da chamada de função atual.
algal
8

Eu tive um problema semelhante em que precisei adicionar uma borda superior e inferior a um UITableViewque redimensiona com base em suas configurações de restrições no UIStoryboard. Consegui acessar as restrições atualizadas com - (void)viewDidLayoutSubviews. Isso é útil para que você não precise criar uma subclasse de uma visualização e substituir seu método de layout.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Sem chamar o método a partir do viewDidLayoutSubviewmétodo, apenas a borda superior é desenhada corretamente, pois a borda inferior está em algum lugar fora da tela.

Brandon Read
fonte
3
viewDidLayoutSubviews sendo chamado algumas vezes, você está criando toneladas de camadas ... Você precisa adicionar algumas verificações de existência antes de adicionar outra camada.
Kalzem
Comente alguns anos depois ... você também deve chamar este método na superclasse ao substituir antes de qualquer outra coisa:[super viewDidLayoutSubviews];
Alejandro Iván
5

Para aqueles que ainda podem enfrentar esses problemas, especialmente com TableviewCell.

Basta substituir o método:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

No caso de UITableViewCell ou UICollectionViewCell, crie uma subclasse da célula e substitua o mesmo método:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}
Mayank Pahuja
fonte
esta é a única maneira de obter o tamanho final real da minha visão
Benoit Jadinon
Esta foi a única maneira de obter o tamanho final de qualquer uma das minhas visualizações restritas, porque antes eu tentava colocá-las em awakeFromNib, o que não dava às subvisualizações tempo para realmente redimensionar primeiro. Obrigado!
Azin Mehrnoosh
3

Use -(void)viewWillAppear:(BOOL)animatede ligue [self.view layoutIfNeeded];- Funciona, eu tentei.

porque se você usá- -(void)viewDidLayoutSubviewslo funcionará definitivamente, mas este método é chamado toda vez que sua IU exige atualizações / mudanças. O que será difícil de gerenciar. A tecla de função é usar uma variável bool para evitar esse loop de chamadas. melhor uso viewWillAppear. Lembre- viewWillAppearse também será chamado se a visualização for carregada de volta (sem realocar).


fonte
2

O quadro ainda é válido. No final, a visualização usa sua propriedade frame para se definir. Ele calcula esse quadro com base em todas as restrições. As restrições são usadas apenas para o layout inicial (e sempre que layoutSubviews é chamado em uma vista, como após uma rotação). Depois disso, as informações de posição estão na propriedade do quadro. Ou você está vendo de outra forma?

Borrrden
fonte
1
Estou vendo o contrário. Os valores do quadro não mudam, mesmo depois de alterar as restrições de largura / altura diretamente (alterando suas constantes. Eu tenho um IBOutlet para eles no xib)
yuf
Meu problema é único porque eu altero a restrição e, em seguida, preciso usar o quadro na mesma função. Chamar setNeedsLayout não o atualiza imediatamente. Acho que preciso esperar pelo layout primeiro e depois usar o quadro. Minha segunda pergunta é: como posso alterar uma restrição de altura / largura se não tenho uma referência direta a ela?
yuf
1
Você deve dispatch_async então, e isso permitirá que você atrase seu código até o próximo quadro. Quanto à segunda parte ... não sei ... você teria que iterar todas as restrições e procurar qual delas é aplicável ... IBOutlets são muito mais fáceis.
borrrden
True para IBOutlets, mas eu quero escrever uma função em uma categoria para um UIView que não terei IBs para trabalhar.
yuf