Quando posso ativar / desativar as restrições de layout?

104

Eu configurei vários conjuntos de restrições em IB e gostaria de alternar programaticamente entre eles dependendo de algum estado. Há uma constraintsAcoleção de outlet, todas marcadas como instaladas do IB, e uma constraintsBcoleção de outlet, todas desinstaladas do IB.

Posso alternar programaticamente entre os dois conjuntos da seguinte maneira:

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

Mas ... eu não consigo descobrir quando fazer isso. Parece que eu deveria ser capaz de fazer isso uma vez viewDidLoad, mas não consigo fazer isso funcionar. Já tentei ligar view.updateConstraints()e view.layoutSubviews()depois definir as restrições, mas sem sucesso.

Descobri que, se definir as restrições, viewDidLayoutSubviewstudo funcionará conforme o esperado. Acho que gostaria de saber duas coisas ...

  1. Por que estou obtendo esse comportamento?
  2. É possível ativar / desativar as restrições de viewDidLoad?
tybro0103
fonte
2
Você quer dizer que deactivateConstraints e activateConstraints funcionaram em viewWillLayoutSubviews? Tentei fazer isso e não funcionou lá ou no viewDidLoad. Funcionou no viewDidAppear; a vista apareceu onde as novas restrições deveriam colocá-la, mas se eu girar para paisagem, a vista voltou para a posição determinada pelas restrições definidas em IB (e permaneceu lá quando girei de volta para retrato). Registrando as restrições, mostrou as corretas (as recém-ativadas). Isso parece um bug para mim.
rdelmar
1
Sim, eles eram válidos (funcionaram em viewDidAppear) e não há necessidade de chamar super porque não há implementação padrão de viewWillLayoutSubviews (tentei chamar super de qualquer maneira, mas não fez diferença).
rdelmar
1
@rdelmar Acabou de ter a chance de testar mais ... Posso verificar se realmente obtive o mesmo comportamento que você descreveu ... funciona primeiro em viewDidAppear, mas depois reverte na rotação.
tybro0103
3
Aparentemente, você não pode marcar restrições como não instaladas no IB para esse propósito. Encontrei essa informação aqui: stackoverflow.com/questions/27663249/… e resolveu o problema para mim.
Stefan
1
Tive minhas restrições implementadas da mesma maneira conforme descrito na pergunta, exceto que eu ativei / desativei algumas delas em viewDidAppear. Isso funcionou, mas você podia ver os elementos mudando rapidamente de posição (um problema menor, mas indesejável). Fazer a alteração em viewWillAppear ou viewDidLoad não funcionou. Mas depois de ler esta pergunta, tentei fazer essa alteração em viewDidLayoutSubviews. Funcionou e a mudança de posição não é mais visível para o usuário. (Também funcionou em viewWillLayoutSubviews). Obrigado por essa dica!
peacetype de

Respostas:

186

Eu ativo e desativo NSLayoutConstraintsem viewDidLoad, e não tenho problemas com isso. Então funciona. Deve haver uma diferença na configuração entre o seu aplicativo e o meu :-)

Vou apenas descrever minha configuração - talvez isso possa lhe dar uma pista:

  1. Eu configurei @IBOutletspara todas as restrições que preciso ativar / desativar.
  2. No ViewController, salvo as restrições em propriedades de classe que não são fracas. A razão para isso é que descobri que, após desativar uma restrição, não consegui reativá-la - ela era nula. Portanto, parece que foi excluído ao ser desativado.
  3. Eu não uso NSLayoutConstraint.deactivate/activatecomo você, eu uso constraint.active = YES/ no NOlugar.
  4. Depois de definir as restrições, eu ligo view.layoutIfNeeded().
Joachim Bøggild
fonte
132
"salve as restrições em propriedades de classe que não sejam fracas" Você me economizou muito tempo, obrigado!
OpenUserX03
10
"Eu salvo as restrições em propriedades de classe que não são fracas": isso me salvou de muita dor de cabeça. Eu não sabia que estava chamando um seletor em um objeto nulo. Obrigado!!
static0886
4
É importante observar que as restrições "inativas" não são ignoradas pelo layout automático, elas são removidas. Ativar / desativar restrições na verdade as adiciona e remove. Passei algum tempo depurando um layout automático conflitante depois de adicionar as restrições que defini anteriormente, .active = falseesperando que fossem ignoradas até que eu as definisse como ativas.
lbarbosa
1
salvar as restrições em propriedades de classe que não são fracas, ok, isso economiza muito tempo, eu estava obtendo alguns resultados mistos sem isso. Obrigado cara!
MegaManX
3
O documento da Apple diz: Ativar ou desativar a restrição chama addConstraint ( :) e removeConstraint ( :) na visão que é o ancestral comum mais próximo dos itens gerenciados por esta restrição. Use esta propriedade em vez de chamar addConstraint ( :) ou removeConstraint ( :) diretamente. Assim, parece que quando uma restrição é desativada, ela é removida e, em seguida, não há referências fortes à restrição à esquerda, a menos que o IBOutlet seja forte. Portanto, a restrição é excluída. IMHO isso é quase um bug ou pelo menos um comportamento muito inesperado.
Olle Raab
52

Talvez você possa verificar o seu @properties, substituir weakporstrong .

Às vezes, porque active = NOdefinido self.yourConstraint = nil, você não poderia usar self.yourConstraintnovamente.

Leo
fonte
5
Conforme declarado no Guia de linguagem Swift , as propriedades são fortes por padrão, então você também pode simplesmente remover weake isso vai resolver.
Jonathan Cabrera de
30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
Tony Swiftguy
fonte
1
Porque meu controlador de visão é um controlador de visão filho - fazê-lo em "didLayoutSubviews" parece ser a única maneira que funcionou !! PARA SUA INFORMAÇÃO.
TalL
Esta é a única resposta válida
Yunus Eren Güzel
@TalL Você quis dizer restrições no próprio controlador de visão filha ou em suas subvisualizações?
Stefan
este é o melhor
ACAkgul
14

Acredito que o problema que você está enfrentando se deve ao fato de as restrições não serem adicionadas às visualizações até que AFTER viewDidLoad()seja chamado. Você tem várias opções:

A) Você pode conectar suas restrições de layout a um IBOutlet e acessá-las em seu código por meio dessas referências. Como as tomadas são conectadas antes do viewDidLoad()início, as restrições devem estar acessíveis e você pode continuar a ativá-las e desativá-las.

B) Se você deseja usar a constraints()função do UIView para acessar as várias restrições, você deve esperar para viewDidLayoutSubviews()iniciar e fazê-lo lá, pois esse é o primeiro ponto após a criação de um controlador de visualização a partir de uma ponta que terá quaisquer restrições instaladas. Não se esqueça de ligar layoutIfNeeded()quando terminar. Isso tem a desvantagem de que a passagem de layout será executada duas vezes se houver alguma alteração a ser aplicada e você deve garantir que não há possibilidade de que um loop infinito seja disparado.

Um breve aviso: restrições desabilitadas NÃO são retornadas pelo constraints() método! Isso significa que se você DESATIVAR uma restrição com a intenção de ativá-la novamente mais tarde, precisará manter uma referência a ela.

C) Você pode esquecer a abordagem do storyboard e adicionar suas restrições manualmente. Como você está fazendo isso em viewDidLoad(), suponho que a intenção seja fazer isso apenas uma vez durante toda a vida útil do objeto, em vez de alterar o layout na hora, então esse deve ser um método aceitável.

Cinza
fonte
10

Você também pode ajustar a prioritypropriedade para "ativar" e "desativá-los" (valor 750 para ativar e 250 para desativar, por exemplo). Por alguma razão, mudar o activeBOOL não teve nenhum efeito na minha IU. Não há necessidade layoutIfNeedede pode ser definido e alterado em viewDidLoad ou a qualquer momento depois disso.

user2387149
fonte
Uma sugestão muito boa. Alterar a prioridade da restrição funciona em viewWillTransition(to:, with:)ou viewWillLayoutSubviews()e você pode manter todas as suas restrições alternativas como "instaladas" em um storyboard. A prioridade da restrição não pode mudar de não obrigatória para obrigatória, portanto, use os valores abaixo 1000. Por outro lado, ativar (adicionar) e desativar (remover) restrições funciona apenas em viewDidLayoutSubviews()e requer a manutenção de strong @IBOutletreferências a NSLayoutConstraint-s.
Gary
"Por alguma razão, alterar o BOOL ativo não teve nenhum efeito na minha IU". Baseado aqui . Acho que você não pode alterar uma restrição com prioridade 1000 durante o tempo de execução. Se você quiser desativá-lo, deve definir a prioridade inicial para 999 ou inferior ....
Honey
Não concordo com essa afirmação, pois pode levar a problemas difíceis de depurar e não responde à pergunta. Definir a prioridade para 250 não "desativará" a restrição, ainda terá efeito e influenciará o layout. Pode parecer que "desativa" a restrição na maioria dos casos, mas definitivamente não em todos os casos. (em particular, não é o caso que me leva a encontrar a resposta para esta pergunta)
Tumata
Isso pode levar a travamentos como "A mutação de uma prioridade de obrigatória para não em uma restrição instalada (ou vice-versa) não é suportada. Você passou a prioridade 250 e a prioridade existente era 1000".
Karthick Ramesh
8

O momento adequado para desativar restrições não utilizadas:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Lembre-se de que isso viewWillLayoutSubviewspode ser chamado várias vezes, então não faça cálculos pesados ​​aqui, certo?

Observação: se você quiser reativar algumas das restrições posteriormente, sempre armazene a strongreferência a elas.

Laszlo
fonte
2
Para mim, a única maneira confiável é ajustar as restrições em viewDidLayoutSubviews(). Ajustar as restrições em viewWillLayoutSubviews()não funciona no meu caso.
petrsyn
6

Quando uma visualização está sendo criada, os seguintes métodos de ciclo de vida são chamados em ordem:

  1. loadView
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

Agora, às suas perguntas.

  1. Por que estou obtendo esse comportamento?

Resposta: Porque quando você tenta definir as restrições nas vistas, viewDidLoada vista não tem seus limites, portanto, as restrições não podem ser definidas. Só depois viewDidLayoutSubviewsque os limites da visualização são finalizados.

  1. É possível ativar / desativar as restrições de viewDidLoad?

Resposta: Não. Motivo explicado acima.

Sumeet
fonte
Em sua descrição do ciclo de vida do viewController, você falou sobre como a visualização é carregada pela primeira vez e, em seguida, viewDidLoad é chamada. No entanto, você também disse que a visualização não é criada no momento em que viewDidLoad é chamada, isso é claramente uma contradição. Além disso, você pode testar você mesmo e ver se a visualização foi criada no momento em que viewDidLoad é chamada, porque você pode adicionar subvisualizações à visualização.
ABakerSmith,
viewDidLoad deve estar bem, já que as visualizações são criadas e carregadas ... Na realidade, onde você ativa as restrições, principalmente se resume ao desempenho. Estou supondo que o problema original não estava relacionado ao local onde as restrições foram ativadas. stackoverflow.com/questions/19387998/…
Gabe
@ABakerSmith editei minha resposta para ser mais claro.
Sumeet
1

Eu descobri, desde que você configure as restrições por normal na substituição de - (void)updateConstraints(objetivo c), com uma strongreferência para a inicialidade usada restrições ativas e não ativas. E em outra parte do ciclo de visualização, desative e / ou ative o que você precisa e, em seguida layoutIfNeeded, ligue para que não haja problemas.

O principal é não reutilizar constantemente a substituição de updateConstraintse separar as ativações das restrições, contanto que você chame updateConstraints após sua primeira inicialização e layout. Depois disso, parece importar onde no ciclo de exibição.

Elliotrock
fonte