UIStackView “Incapaz de satisfazer simultaneamente as restrições” em visualizações ocultas “esmagadas”

95

Quando minhas "linhas" UIStackView são comprimidas, elas jogam AutoLayout avisos. No entanto, eles são exibidos corretamente e nada mais está errado além desses tipos de registros:

Incapaz de satisfazer as restrições simultaneamente. Provavelmente, pelo menos uma das restrições na lista a seguir é aquela que você não deseja. Experimente o seguinte: (1) observe cada restrição e tente descobrir qual você não espera; (2) encontre o código que adicionou a restrição ou restrições indesejadas e corrija-o. (Observação: se você perceber NSAutoresizingMaskLayoutConstraintsque não entende, consulte a documentação da UIViewpropriedade translatesAutoresizingMaskIntoConstraints) (

Então, eu não tenho certeza de como consertar isso ainda, mas não parece quebrar nada além de ser irritante.

Alguém sabe como resolver isso? Curiosamente, as restrições de layout são frequentemente marcadas com 'ocultação de UISV' , indicando que talvez deva ignorar os mínimos de altura para subvisualizações ou algo assim neste caso.

Ben Guild
fonte
1
Isso parece ter sido corrigido no iOS11, não recebendo nenhum aviso aqui
trapper

Respostas:

204

Você tem esse problema porque ao definir uma subvisualização de dentro UIStackViewpara oculta, ela primeiro restringirá sua altura a zero para animá-la.

Eu estava recebendo o seguinte erro:

2015-10-01 11:45:13.732 <redacted>[64455:6368084] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

O que eu estava tentando fazer era colocar um UIViewdentro do meu UIStackViewque continha uma UISegmentedControlinserção de 8pts em cada borda.

Quando eu defini como oculto, ele tentaria restringir a visualização do contêiner a uma altura zero, mas como eu tenho um conjunto de restrições de cima para baixo, houve um conflito.

Para resolver o problema, mudei minha prioridade de restrição superior e inferior de 8pt de 1000 para 999 para que a UISV-hidingrestrição possa ter prioridade, se necessário.

liamnichols
fonte
deve-se observar que parece que se você apenas tiver uma restrição de altura ou largura e reduzir sua prioridade, isso não funcionará. Você precisa remover a altura / largura e adicionar os fundos à direita superiores e, em seguida, definir sua prioridade como inferior e funciona
bolnad de
4
Mudar as prioridades também funcionou para mim. Além disso, removendo quaisquer restrições em excesso (esmaecidas) que foram acidentalmente copiadas de classes de tamanho não utilizadas. DICA IMPORTANTE: para depurar mais facilmente esses problemas, defina uma string IDENTIFER em cada restrição. Então você pode ver qual restrição estava sendo danosa na mensagem de depuração.
Womble
3
No meu caso, só preciso diminuir a prioridade na altura e funciona.
pixelfreak
Essa dica do IDENTIFICADOR é ótima! Sempre me perguntei como dar as restrições nos nomes das mensagens de depuração, sempre procurava adicionar algo à visualização em vez da própria restrição. Obrigado @Womble!
Ryan
Obrigado! A prioridade de 1000 a 999 resolveu o problema. Código X: Versão 8.3.3 (8E3004b)
Michael Garito
51

Eu estava tendo um problema semelhante que não era fácil de resolver. No meu caso, eu tinha uma visão de pilha embutida em uma visão de pilha. O UIStackView interno tinha dois rótulos e um espaçamento diferente de zero especificado.

Quando você chama addArrangedSubview (), ele cria automaticamente restrições semelhantes às seguintes:

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

Agora, quando você tenta ocultar o innerStackView, recebe um aviso de restrições ambíguas.

Para entender o porquê, primeiro vamos ver por que isso não acontece quando innerStackView.spacingé igual a 0. Quando você chama innerStackView.hidden = true, @liamnichols estava correto ... o outerStackViewirá interceptar magicamente esta chamada e criar uma restrição de ocultação de UISV de0 altura com prioridade 1000 (obrigatório). Presumivelmente, isso permite que os elementos na visualização da pilha sejam animados fora da visualização, no caso de seu código oculto ser chamado dentro de um bloco. Infelizmente, não parece haver uma maneira de evitar que essa restrição seja adicionada. No entanto, você não receberá um aviso "Incapaz de satisfazer as restrições simultaneamente" (USSC), uma vez que acontece o seguinte:UIView.animationWithDuration()

  1. a altura do rótulo1 é definida como 0
  2. o espaçamento entre os dois rótulos já foi definido como 0
  3. a altura de label2 é definida como 0
  4. a altura do innerStackView é definida como 0

É claro que essas 4 restrições podem ser satisfeitas. A visualização em pilha simplesmente transforma tudo em um pixel de altura 0.

Agora voltando ao exemplo de buggy, se definir o spacingque 2, agora temos essas restrições:

  1. a altura do rótulo1 é definida como 0
  2. o espaçamento entre os dois rótulos foi criado automaticamente pela visualização da pilha como 2 pixels de altura com prioridade de 1000.
  3. a altura de label2 é definida como 0
  4. a altura do innerStackView é definida como 0

A visualização da pilha não pode ter 0 pixels de altura e seu conteúdo ter 2 pixels de altura. As restrições não podem ser satisfeitas.

Observação: você pode ver esse comportamento com um exemplo mais simples. Basta adicionar um UIView a uma exibição de pilha como uma subvisualização organizada. Em seguida, defina uma restrição de altura nesse UIView com prioridade 1000. Agora tente chamar hide sobre isso.

Nota: Por alguma razão, isso só acontecia quando minha visão de pilha era uma subvisão de UICollectionViewCell ou UITableViewCell. No entanto, você ainda pode reproduzir esse comportamento fora de uma célula chamando innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)no próximo loop de execução depois de ocultar a exibição da pilha interna.

Nota: Mesmo se você tentar executar o código em um UIView.performWithoutAnimations, a visualização da pilha ainda adicionará uma restrição de altura 0 que causará o aviso USSC.


Existem pelo menos 3 soluções para este problema:

  1. Antes de ocultar qualquer elemento em uma visão de pilha, verifique se é uma visão de pilha e, em caso afirmativo, altere o spacingpara 0. Isso é irritante porque você precisa reverter o processo (e lembrar o espaçamento original) sempre que mostrar o conteúdo novamente.
  2. Em vez de ocultar elementos em uma exibição de pilha, chame removeFromSuperview. Isso é ainda mais irritante, pois ao reverter o processo, você precisa se lembrar onde inserir o item removido. Você poderia otimizar apenas chamando removeArrangedSubview e, em seguida, ocultando, mas ainda há muitos registros que precisam ser feitos.
  3. Envolva visualizações de pilha aninhadas (que não têm zero spacing) em um UIView. Especifique pelo menos uma restrição como prioridade não obrigatória (999 ou abaixo). Esta é a melhor solução, já que não é necessário fazer nenhuma contabilidade. No meu exemplo, criei as restrições superior, inicial e final em 1000 entre a visualização da pilha e a visualização do wrapper e, em seguida, criei uma restrição 999 da parte inferior da visualização da pilha até a visualização do wrapper. Dessa forma, quando a visualização da pilha externa cria uma restrição de altura zero, a restrição 999 é quebrada e você não vê o aviso USSC. (Observação: isso é semelhante à solução para o caso de contentView.translatesAutoResizingMaskToConstraints de uma subclasse UICollectionViewCell ser definido comofalse )

Em resumo, os motivos pelos quais você obtém esse comportamento são:

  1. A Apple cria automaticamente 1000 restrições de prioridade quando você adiciona subvisualizações gerenciadas a uma visão de pilha.
  2. A Apple cria automaticamente uma restrição de altura 0 para você quando você oculta uma subvisualização de uma visão de pilha.

Se a Apple (1) tivesse permitido que você especificasse a prioridade das restrições (especialmente de espaçadores) ou (2) tivesse permitido que você desistisse da restrição automática de ocultação de UISV , este problema seria facilmente resolvido.

Sensível
fonte
6
Obrigado pela explicação super completa e útil. No entanto, isso definitivamente parece um bug do lado da Apple com visões de pilha. Basicamente, o recurso de "ocultar" é incompatível com o recurso de "espaçamento". Alguma idéia de que eles resolveram isso desde então, ou adicionaram alguns recursos para evitar invasões com visualizações extras de contenção? (Mais uma vez, grande quebra de possíveis soluções e não concordam com a elegância de # 3)
Marchy
1
Cada UIStackViewfilho de uma UIStackViewprecisa ser envolvido em um UIViewou apenas aquele que você está tentando ocultar / revelar?
Adrian de
1
Isso parece um descuido muito ruim por parte da Apple. Especialmente porque os avisos e erros relacionados ao uso de UIStackViewtendem a ser enigmáticos e difíceis de entender.
bompf
Este é um salva-vidas. Eu estava tendo um problema onde UIStackViews adicionados a um UITableViewCell estavam causando spam no log de erros do AutoLayout, toda vez que a célula era reutilizada. Incorporar o stackView em um UIView com sua prioridade de restrição de âncora inferior definida como baixa, resolveu o problema. Isso faz com que o depurador de visualização mostre os elementos do stackView como tendo alturas ambíguas, mas é mostrado corretamente no aplicativo, sem spam de log de erro. OBRIGADO.
Womble
6

Na maioria das vezes, esse erro pode ser resolvido diminuindo a prioridade das restrições para eliminar conflitos.

Luciano Almeida
fonte
O que você quer dizer? As restrições são todas relativas nas visualizações empilhadas ...
Ben Guild
Sinto muito, não entendi sua pergunta direito, meu inglês é tão bom, mas o que eu acho é que as restrições são relativas à visão a que está associada, se a visão ficar oculta as restrições se tornaram inativas. Não tenho certeza se essa é a sua dúvida, mas espero poder ajudar.
Luciano Almeida
Parece vir de quando o material é espremido ou em processo de ser mostrado / escondido. Nesse caso, fica parcialmente visível. - Talvez seja necessário percorrer e eliminar quaisquer constantes verticais mínimas porque pode ser esmagado até a altura 0?
Ben Guild
2

Quando você define uma visualização como oculta, o UIStackview tentará animá-la. Se você quiser esse efeito, precisará definir a prioridade certa para as restrições para que não entrem em conflito (como muitos sugeriram acima).

No entanto, se você não se importa com a animação (talvez a esteja escondendo em ViewDidLoad), então você pode simplificar, o removeFromSuperviewque terá o mesmo efeito, mas sem problemas com restrições, já que elas serão removidas junto com a visualização.

Oren
fonte
1

Com base na resposta de @ Senseful, aqui está uma extensão UIStackView para envolver uma visualização de pilha em uma visualização e aplicar as restrições que ele recomenda:

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

Em vez de adicionar seu stackView, use stackView.wrapped().

Ben Packard
fonte
1

Primeiro, como outros sugeriram, certifique-se de que as restrições que você pode controlar, ou seja, não as restrições inerentes ao UIStackView, estão definidas com a prioridade 999, para que possam ser substituídas quando a visualização estiver oculta.

Se você ainda estiver tendo o problema, é provável que o problema seja devido ao espaçamento nas StackViews ocultas. Minha solução foi adicionar um UIView como espaçador e definir o espaçamento UIStackView como zero. Em seguida, defina as restrições View.height ou View.width (dependendo de uma pilha vertical ou horizontal) para o espaçamento de StackView.

Em seguida, ajuste as prioridades de envolvimento de conteúdo e resistência à compressão de conteúdo de suas visualizações recém-adicionadas. Você também pode ter que alterar a distribuição do StackView pai.

Todos os itens acima podem ser feitos no Interface Builder. Além disso, você pode ter que ocultar / mostrar algumas das visualizações recém-adicionadas programaticamente para que não haja espaçamento indesejado.

Peter Coyle
fonte
1

Recentemente, lutei com erros de layout automático ao ocultar um UIStackView. Em vez de fazer um monte de guarda de livros e empacotamento UIViews, optei por criar uma saída para o meu parentStackViewe para as crianças que quero ocultar / mostrar.

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

No storyboard, minha pilha pai se parece com:

insira a descrição da imagem aqui

Ele tem 4 filhos e cada um deles tem várias visualizações de pilha dentro deles. Quando você oculta uma visualização de pilha, se ela tiver elementos de interface do usuário que também são visualizações de pilha, você verá um fluxo de erros de layout automático. Em vez de me esconder, optei por removê-los.

No meu exemplo, parentStackViewscontém uma matriz dos 4 elementos: Top Stack View, StackViewNumber1, Stack View número 2 e botão Stop. Seus índices arrangedSubviewssão 0, 1, 2 e 3, respectivamente. Quando desejo ocultar um, simplesmente o removo do parentStackView's arrangedSubviewsarray. Como não é fraco, ele permanece na memória e você pode simplesmente colocá-lo de volta no índice desejado mais tarde. Não estou reinicializando-o, então ele permanece até que seja necessário, mas não incha a memória.

Então, basicamente, você pode ...

1) Arraste IBOutlets para a pilha pai e os filhos que deseja ocultar / mostrar para o storyboard.

2) Quando você quiser ocultá-los, remova a pilha que deseja ocultar do parentStackView's arrangedSubviewsarray.

3) Ligue self.view.layoutIfNeeded()com UIView.animateWithDuration.

Observe que os dois últimos stackViews não são weak. Você precisa mantê-los por perto para quando você os revelar.

Digamos que eu queira ocultar stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Em seguida, anime-o:

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Se você quiser "mostrar" um stackViewNumber2depois, basta inseri-lo no parentStackView arrangedSubViewsíndice desejado e animar a atualização.

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Descobri que isso é muito mais fácil do que fazer contabilidade sobre as restrições, mexer nas prioridades etc.

Se você deseja ocultar algo por padrão, pode simplesmente colocá-lo no storyboard, removê-lo viewDidLoade atualizá-lo sem usar a animação view.layoutIfNeeded().

Adrian
fonte
1

Eu experimentei os mesmos erros com Stack Views incorporado, embora tudo funcionasse bem no tempo de execução.

Resolvi os erros de restrição ocultando todas as visualizações da subpilha primeiro (configuração isHidden = true) antes de ocultar a visualização da pilha pai.

Fazer isso não envolvia toda a complexidade de remover visualizações suborganizadas, mantendo um índice para quando fosse necessário adicioná-las novamente.

Espero que isto ajude.

Will Stevens
fonte
1

A Senseful forneceu uma excelente resposta para a raiz do problema acima, então irei direto para a solução.

Tudo o que você precisa fazer é definir a prioridade de todas as restrições stackView abaixo de 1000 (999 fará o trabalho). Por exemplo, se stackView estiver restrito à esquerda, direita, superior e inferior para sua supervisão, todas as 4 restrições devem ter prioridade inferior a 1000.

Linh Ta
fonte
0

Você pode ter criado uma restrição ao trabalhar com uma determinada classe de tamanho (ex: wCompact hRegular) e, em seguida, criado uma duplicata quando alternou para outra classe de tamanho (ex: wAny hAny). verifique as restrições dos objetos de interface do usuário em classes de tamanho diferentes e veja se há anomalias nas restrições. você deve ver as linhas vermelhas indicando restrições de colisão. Não posso colocar uma foto antes de obter 10 pontos de reputação, desculpe: /

FM
fonte
Ah ok. mas recebi aquele erro quando tive o caso que descrevi drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…
FM
Sim, definitivamente não estou vendo nenhum vermelho ao alternar as classes de tamanho no construtor de interface. Usei apenas o tamanho "Qualquer".
Ben Guild
0

Eu queria ocultar UIStackViews inteiros de uma vez, mas estava recebendo os mesmos erros do OP, isso corrigiu para mim:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}
sp00ky
fonte
Isso não funcionou para mim porque o mecanismo de layout automático reclama quando você altera uma restrição obrigatória ( priority = 1000) para não obrigatória ( priority <= 999).
Senseful
0

Eu tinha uma linha de botões com restrição de altura. Isso acontece quando um botão está oculto. Definir a prioridade dessa restrição de altura dos botões para 999 resolveu o problema.

Niklas
fonte
-2

Este erro não tem nada a ver com UIStackView. Acontece quando você tem restrições de conflito com as mesmas prioridades. Por exemplo, se você tiver uma restrição afirma que a largura da sua visualização é 100, e você tem outra restrição ao mesmo tempo que afirma que a largura da visualização é 25% de seu contêiner. Obviamente, existem duas restrições conflitantes. A solução é excluir um deles.

William Kinaan
fonte
-3

NOP com [mySubView removeFromSuperview]. Espero que possa ajudar alguém :)

Grégoire GUYON
fonte
Desculpe, o que é NOP?
Pang