Remova todas as restrições que afetam um UIView

87

Eu tenho um UIView que é colocado na tela por meio de várias restrições. Algumas das restrições são propriedade do superview, outras são propriedade de outros ancestrais (por exemplo, talvez a propriedade view de um UIViewController).

Quero remover todas essas restrições antigas e colocá-las em algum lugar novo usando novas restrições.

Como posso fazer isso sem criar um IBOutlet para cada restrição e ter que lembrar qual visualização possui essa restrição?

Para elaborar, a abordagem ingênua seria criar um monte de IBOutlets para cada uma das restrições e envolveria códigos de chamada, como:

[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];

O problema com esse código é que mesmo no caso mais simples, eu precisaria criar 2 IBOutlets. Para layouts complexos, isso pode facilmente chegar a 4 ou 8 IBOutlets necessários. Além disso, eu precisaria garantir que minha chamada para remover a restrição seja feita na visualização adequada. Por exemplo, imagine que myViewsLeftConstrainté propriedade de viewA. Se eu ligasse acidentalmente [self.view removeConstraint:self.myViewsLeftConstraint], nada aconteceria.

Observação: o método constraintsAffectingLayoutForAxis parece promissor, mas destina-se apenas a fins de depuração.


Update: Muitas das respostas que eu estou recebendo acordo com self.constraints, self.superview.constraintsou alguma variante das pessoas. Essas soluções não funcionarão, pois esses métodos retornam apenas as restrições pertencentes à visualização, não aquelas que afetam a visualização.

Para esclarecer o problema com essas soluções, considere esta hierarquia de visualização:

  • Avô
    • Pai
      • Eu
        • Filho
        • Filha
      • Irmão
    • Tio

Agora imagine que criamos as seguintes restrições e sempre as anexamos ao ancestral comum mais próximo:

  • C0: Me: mesmo top que Son (propriedade de mim)
  • C1: Me: largura = 100 (propriedade de mim)
  • C2: Eu: mesma altura do irmão (propriedade do pai)
  • C3: Eu: mesmo top que o tio (propriedade do avô)
  • C4: Eu: igual à esquerda do avô (propriedade do avô)
  • C5: Irmão: igual à esquerda do Pai (propriedade do Pai)
  • C6: Tio: igual à esquerda do Avô (propriedade do Avô)
  • C7: Filho: igual à filha (propriedade de mim)

Agora imagine que queremos remover todas as restrições que afetam Me. Qualquer solução adequada deve remover [C0,C1,C2,C3,C4]e nada mais.

Se eu usar self.constraints(onde self sou eu), vou conseguir [C0,C1,C7], uma vez que essas são as únicas restrições que tenho . Obviamente, não seria suficiente removê-lo, pois está faltando [C2,C3,C4]. Além disso, ele está removendo C7desnecessariamente.

Se eu usar self.superview.constraints(onde eu sou eu), vou conseguir [C2,C5], uma vez que essas são as restrições de propriedade do pai. Obviamente, não podemos remover tudo isso, pois não tem nenhuma C5relação com Me.

Se eu usar grandfather.constraints, vou conseguir [C3,C4,C6]. Novamente, não podemos remover todos eles, pois C6devem permanecer intactos.

A abordagem de força bruta consiste em percorrer cada um dos ancestrais da visualização (incluindo ela mesma) e ver se firstItemou secondItemsão a própria visualização; em caso afirmativo, remova essa restrição. Isso levará a uma solução correta, retornando [C0,C1,C2,C3,C4], e apenas essas restrições.

No entanto, espero que haja uma solução mais elegante do que ter que percorrer toda a lista de ancestrais.

Sensível
fonte
Que tal você colocar um identificador para todas as restrições que deseja remover? Dessa forma, você não precisa manter uma saída para eles.
nsuinteger

Respostas:

69

Essa abordagem funcionou para mim:

@interface UIView (RemoveConstraints)

- (void)removeAllConstraints;

@end


@implementation UIView (RemoveConstraints)

- (void)removeAllConstraints
{
    UIView *superview = self.superview;
    while (superview != nil) {
        for (NSLayoutConstraint *c in superview.constraints) {
            if (c.firstItem == self || c.secondItem == self) {
                [superview removeConstraint:c];
            }
        }
        superview = superview.superview;
    }

    [self removeConstraints:self.constraints];
    self.translatesAutoresizingMaskIntoConstraints = YES;
}

@end

Depois de concluída a execução, sua visualização permanece onde estava porque cria restrições de redimensionamento automático. Quando não faço isso, a visão geralmente desaparece. Além disso, ele não apenas remove as restrições do supervisualização, mas percorre todo o caminho para cima, pois pode haver restrições afetando-o nas visões ancestrais.


Versão Swift 4

extension UIView {
    
    public func removeAllConstraints() {
        var _superview = self.superview
        
        while let superview = _superview {
            for constraint in superview.constraints {
                
                if let first = constraint.firstItem as? UIView, first == self {
                    superview.removeConstraint(constraint)
                }
                
                if let second = constraint.secondItem as? UIView, second == self {
                    superview.removeConstraint(constraint)
                }
            }
            
            _superview = superview.superview
        }
        
        self.removeConstraints(self.constraints)
        self.translatesAutoresizingMaskIntoConstraints = true
    }
}
marchinram
fonte
3
Esta deve ser a resposta oficial.
Jason Crocker
por que você tem self.translatesAutoresizingMaskIntoConstraints = YES; ? você literalmente nunca quer isso para visualizações que está configurando com restrições?
lensovet
4
Estou removendo as restrições, então quero usar as restrições de
redimensionamento automático
Eu precisava registrar todas as restrições em uma visualização para fins de depuração e fui capaz de modificar ligeiramente esta resposta para fazer isso. +1
chiliNUT
Isso removeria incorretamente C7. No entanto, deve ser fácil de corrigir se você remover [self removeConstraints:self.constraints];e alterar UIView *superview = self.superview;para UIView *superview = self;.
Senseful
49

A única solução que encontrei até agora é remover a visualização de sua supervisão:

[view removeFromSuperview]

Parece que ele remove todas as restrições que afetam seu layout e está pronto para ser adicionado a uma visualização e ter novas restrições anexadas. No entanto, ele removerá incorretamente quaisquer subvisualizações da hierarquia e eliminará [C7]incorretamente.

Sensível
fonte
Você pode usar as propriedades firstItem e secondItem das restrições para verificar se elas se aplicam à sua visualização e percorrer a hierarquia de visualização para localizar todas elas. No entanto, acho que remover e adicionar novamente é uma solução melhor. Eu estaria interessado em ver seu caso de uso, pois parece que isso é uma coisa muito ruim de se fazer. Suas restrições podem facilmente se tornar completamente inválidas, uma vez que outras visualizações podem estar contando com essas restrições para um layout adequado.
bjtitus
@bjtitus: Bom ponto. No entanto, neste caso específico, estou reposicionando uma visão que nada depende dela. Ele usa outras visualizações para saber onde ser colocado, e não há visualizações que o utilizem para saber onde ser colocado.
Senseful
Não é a única solução, mas muito simples. Em meu caso de uso, acabei de remover a visão da supervisão e a adicionei novamente mais tarde.
Marián Černý
48

Você pode remover todas as restrições em uma visualização fazendo o seguinte:

self.removeConstraints(self.constraints)

EDITAR: Para remover as restrições de todas as subvisualizações, use a seguinte extensão no Swift:

extension UIView {
    func clearConstraints() {
        for subview in self.subviews {
            subview.clearConstraints()
        }
        self.removeConstraints(self.constraints)
    }
}
emdog4
fonte
22
O problema com essa solução é que ela remove apenas as restrições que essa visualização possui (por exemplo, sua largura e altura). Não remove coisas como as restrições principais e superiores.
Senseful
2
Eu não verifiquei seu comentário acima. No entanto, acredito que isso funcionaria da mesma forma ou melhor do que acima, [NSLayoutConstraint deactivateConstraints: self.constraints];
emdog4
2
Isso teria o mesmo problema da solução original. self.constraintsretorna apenas as restrições que selfpossui, não todas as restrições que afetam self.
Senseful
1
Eu entendo o que você está dizendo agora. Em minha resposta , estou removendo as restrições do contentView do UITableViewCell. O método updateConstraints então adicionaria restrições para todas as subvisualizações e redefiniria o layout. Em meu primeiro comentário acima, eu deveria ter digitado self.view.constraints. Tanto self.view quanto self.contentView estão no topo da hierarquia de visualizações. Definir translatesAutoresizingMasksToConstraints = YES em self.view ou self.contentView é uma má ideia. ;-). Eu não gosto de chamadas para addSubview: no meu método updateConstraints, remover a visão da hierarquia parece desnecessário.
emdog4,
Acabei de atualizar a pergunta para mostrar por que uma solução como essa não funciona para o que estou procurando. Supondo que você esteja em um UITableViewCell, o equivalente a contentView é Grandfather. Portanto, sua solução removeria [C6]incorretamente e não removeria incorretamente [C0,C1,C2].
Senseful
20

Existem duas maneiras de conseguir isso de acordo com a documentação do desenvolvedor da Apple

1 NSLayoutConstraint.deactivateConstraints

Este é um método conveniente que fornece uma maneira fácil de desativar um conjunto de restrições com uma chamada. O efeito desse método é o mesmo que definir a propriedade isActive de cada restrição como false. Normalmente, usar esse método é mais eficiente do que desativar cada restrição individualmente.

// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])

// Usage
NSLayoutConstraint.deactivate(yourView.constraints)

2. UIView.removeConstraints(Obsoleto para> = iOS 8.0)

Ao desenvolver para iOS 8.0 ou posterior, use o método deactivateConstraints: da classe NSLayoutConstraint em vez de chamar o método removeConstraints: diretamente. O método deactivateConstraints: remove automaticamente as restrições das visualizações corretas.

// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])`

// Usage
yourView.removeConstraints(yourView.constraints)

Dicas

Usar Storyboards ou XIBs pode ser uma dor de cabeça para configurar as restrições conforme mencionado em seu cenário, você deve criar IBOutlets para cada um que deseja remover. Mesmo assim, na maioria das vezes Interface Buildercria mais problemas do que resolve.

Portanto, ao ter um conteúdo muito dinâmico e diferentes estados de visão, eu sugeriria:

  1. Criação de suas visualizações de maneira programática
  2. Faça o layout e use NSLayoutAnchor
  3. Anexe cada restrição que pode ser removida posteriormente a uma matriz
  4. Limpe-os sempre antes de aplicar o novo estado

Código Simples

private var customConstraints = [NSLayoutConstraint]()

private func activate(constraints: [NSLayoutConstraint]) {
    customConstraints.append(contentsOf: constraints)
    customConstraints.forEach { $0.isActive = true }
}

private func clearConstraints() {
    customConstraints.forEach { $0.isActive = false }
    customConstraints.removeAll()
}

private func updateViewState() {
    clearConstraints()

    let constraints = [
        view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
        view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
        view.topAnchor.constraint(equalTo: parentView.topAnchor),
        view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
    ]

    activate(constraints: constraints)

    view.layoutIfNeeded()
}

Referências

  1. NSLayoutConstraint
  2. UIView
E-Riddie
fonte
2
Seja cuidadoso. Visualizações que especificam um tamanho de conteúdo intrínseco terão NSContentSizeLayoutConstraint adicionado automaticamente pelo iOS. Removê-lo acessando a propriedade normal yourView.constraints interromperá o autolayout nessas visualizações.
TigerCoding
O problema com essa solução é que ela remove apenas as restrições que essa visualização possui (por exemplo, sua largura e altura). Não remove coisas como as restrições principais e superiores.
Senseful
Sim, o que Senseful disse. Embora esses métodos existam, eles não resolvem sozinhos o problema sobre o qual a questão está realmente se perguntando.
GSnyder
18

Em Swift:

import UIKit

extension UIView {

    /**
     Removes all constrains for this view
     */
    func removeConstraints() {

        let constraints = self.superview?.constraints.filter{
            $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
        } ?? []

        self.superview?.removeConstraints(constraints)
        self.removeConstraints(self.constraints)
    }
}
Alexander Volkov
fonte
Isso irá travar se a sua visualização não tiver superview em 'superview!'. Por favor, envolva a parte em um 'if let superview = superview' :)
Bersaelor
1
Isso não removerá todas as restrições. As restrições usam o pai mais próximo das 2 visualizações afetadas, portanto, se você fizer uma restrição que não compartilha da visão geral desta visualização, essa restrição não será removida ...
vrwim
2
talvez mais correto seria comparar a identidade em if c.firstItem === selfvez de lançar as? UIViewe verificar a igualdade com==
user1244109
Provavelmente não deve usar removeConstraints: from apple doc: // O método removeConstraints será descontinuado em uma versão futura e deve ser evitado. Em vez disso, use + [NSLayoutConstraint deactivateConstraints:].
eonist
@eonist a resposta é há 3 anos. Em vez de críticas, você deve editá-lo.
Alexander Volkov
5

Detalhes

  • Xcode 10.2.1 (10E1001), Swift 5

Solução

import UIKit

extension UIView {

    func removeConstraints() { removeConstraints(constraints) }
    func deactivateAllConstraints() { NSLayoutConstraint.deactivate(getAllConstraints()) }
    func getAllSubviews() -> [UIView] { return UIView.getAllSubviews(view: self) }

    func getAllConstraints() -> [NSLayoutConstraint] {
        var subviewsConstraints = getAllSubviews().flatMap { $0.constraints }
        if let superview = self.superview {
            subviewsConstraints += superview.constraints.compactMap { (constraint) -> NSLayoutConstraint? in
                if let view = constraint.firstItem as? UIView, view == self { return constraint }
                return nil
            }
        }
        return subviewsConstraints + constraints
    }

    class func getAllSubviews(view: UIView) -> [UIView] {
        return view.subviews.flatMap { [$0] + getAllSubviews(view: $0) }
    }
}

Uso

print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()
Vasily Bodnarchuk
fonte
1
É muito bom.
eonist
2

Uma solução rápida:

extension UIView {
  func removeAllConstraints() {
    var view: UIView? = self
    while let currentView = view {
      currentView.removeConstraints(currentView.constraints.filter {
        return $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
      })
      view = view?.superview
    }
  }
}

É importante examinar todos os pais, uma vez que as restrições entre dois elementos são mantidas pelos ancestrais comuns, portanto, apenas limpar a supervisão conforme detalhado nesta resposta não é bom o suficiente, e você pode acabar tendo uma surpresa ruim mais tarde.

Guig
fonte
2

Com base em respostas anteriores (swift 4)

Você pode usar InstantConstraints quando não quiser rastrear hierarquias inteiras.

extension UIView {
/**
 * Deactivates immediate constraints that target this view (self + superview)
 */
func deactivateImmediateConstraints(){
    NSLayoutConstraint.deactivate(self.immediateConstraints)
}
/**
 * Deactivates all constrains that target this view
 */
func deactiveAllConstraints(){
    NSLayoutConstraint.deactivate(self.allConstraints)
}
/**
 * Gets self.constraints + superview?.constraints for this particular view
 */
var immediateConstraints:[NSLayoutConstraint]{
    let constraints = self.superview?.constraints.filter{
        $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
        } ?? []
    return self.constraints + constraints
}
/**
 * Crawls up superview hierarchy and gets all constraints that affect this view
 */
var allConstraints:[NSLayoutConstraint] {
    var view: UIView? = self
    var constraints:[NSLayoutConstraint] = []
    while let currentView = view {
        constraints += currentView.constraints.filter {
            return $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
        }
        view = view?.superview
    }
    return constraints
}
}
eonista
fonte
2

Eu uso o seguinte método para remover todas as restrições de uma visualização:

arquivo .h:

+ (void)RemoveContraintsFromView:(UIView*)view 
    removeParentConstraints:(bool)parent 
    removeChildConstraints:(bool)child;

arquivo .m:

+ (void)RemoveContraintsFromView:(UIView *)view 
    removeParentConstraints:(bool)parent 
    removeChildConstraints:(bool)child
{
    if (parent) {
        // Remove constraints between view and its parent.
        UIView *superview = view.superview;
        [view removeFromSuperview];
        [superview addSubview:view];
    }

    if (child) {
        // Remove constraints between view and its children.
        [view removeConstraints:[view constraints]];
    }
}

Você também pode ler este post no meu blog para entender melhor como funciona nos bastidores.

Se você precisar de um controle mais granular, eu recomendo fortemente que você mude para a Maçonaria , uma classe de estrutura poderosa que você pode usar sempre que precisar lidar com restrições de maneira adequada de forma programática.

Darkseal
fonte
2
E irmãos?
zrslv
2

A abordagem mais fácil e eficiente é remover a visualização de superView e adicionar novamente como subvisualização. isso faz com que todas as restrições de subvisualização sejam removidas automaticamente.😉

Hashem Aboonajmi
fonte
1

Com objetivo C

[self.superview.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
        if (constraint.firstItem == self || constraint.secondItem == self) {
            [self.superview removeConstraint:constraint];
        }
    }];
    [self removeConstraints:self.constraints];
}
Giang
fonte
0

Você poderia usar algo assim:

[viewA.superview.constraints enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
    if (constraint.firstItem == viewA || constraint.secondItem == viewA) {
        [viewA.superview removeConstraint:constraint];
    }
}];

[viewA removeConstraints:viewA.constraints];

Basicamente, ele enumera todas as restrições na superview de viewA e remove todas as restrições relacionadas a viewA.

Em seguida, a segunda parte remove as restrições de viewA usando a matriz de restrições de viewA.

Tristan
fonte
1
O problema com esta solução é que ela remove apenas as restrições pertencentes ao superview. Ele não remove todas as restrições que afetam a vista. (Por exemplo, você poderia facilmente configurar restrições no superview do superview, que não seriam removidas com esta solução.)
Senseful
Acabei de atualizar a pergunta para mostrar por que essa solução não funciona. Isso é o mais próximo de ser correto, pois é o mais próximo da solução de força bruta que mencionei acima. No entanto, esta solução irá remover incorretamente [C7]e não removerá incorretamente [C3,C4].
Senseful
você poderia projetar o método para rastrear a hierarquia. Mas essa solução é boa o suficiente, já que você deve adicionar restrições acima da supervisão imediata de qualquer maneira. IMO
eonist
0

(Em 31 de julho de 2017)

SWIFT 3

self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)

Objetivo C

[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];

Esta é a maneira mais fácil de remover rapidamente todas as restrições existentes em um UIView. Apenas certifique-se de adicionar o UIView de volta com suas novas restrições ou novo quadro depois =)

BennyTheNerd
fonte
0

Usando uma sequência reutilizável

Decidi abordar isso de uma forma mais 'reutilizável'. Visto que encontrar todas as restrições que afetam uma visualização é a base para todos os itens acima, decidi implementar uma sequência customizada que retorna todos eles para mim, junto com as visualizações proprietárias.

A primeira coisa a fazer é definir uma extensão no Arraysde NSLayoutConstraintque retorna todos os elementos que afetam uma visão específica.

public extension Array where Element == NSLayoutConstraint {

    func affectingView(_ targetView:UIView) -> [NSLayoutConstraint] {

        return self.filter{

            if let firstView = $0.firstItem as? UIView,
                firstView == targetView {
                return true
            }

            if let secondView = $0.secondItem as? UIView,
                secondView == targetView {
                return true
            }

            return false
        }
    }
}

Em seguida, usamos essa extensão em uma sequência personalizada que retorna todas as restrições que afetam essa visualização, juntamente com as visualizações que realmente as possuem (que podem estar em qualquer lugar acima na hierarquia de visualização)

public struct AllConstraintsSequence : Sequence {

    public init(view:UIView){
        self.view = view
    }

    public let view:UIView

    public func makeIterator() -> Iterator {
        return Iterator(view:view)
    }

    public struct Iterator : IteratorProtocol {

        public typealias Element = (constraint:NSLayoutConstraint, owningView:UIView)

        init(view:UIView){
            targetView  = view
            currentView = view
            currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
        }

        private let targetView  : UIView
        private var currentView : UIView
        private var currentViewConstraintsAffectingTargetView:[NSLayoutConstraint] = []
        private var nextConstraintIndex = 0

        mutating public func next() -> Element? {

            while(true){

                if nextConstraintIndex < currentViewConstraintsAffectingTargetView.count {
                    defer{nextConstraintIndex += 1}
                    return (currentViewConstraintsAffectingTargetView[nextConstraintIndex], currentView)
                }

                nextConstraintIndex = 0

                guard let superview = currentView.superview else { return nil }

                self.currentView = superview
                self.currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
            }
        }
    }
}

Finalmente, declaramos uma extensão UIViewativada para expor todas as restrições que a afetam em uma propriedade simples que você pode acessar com uma sintaxe simples para cada.

extension UIView {

    var constraintsAffectingView:AllConstraintsSequence {
        return AllConstraintsSequence(view:self)
    }
}

Agora podemos iterar todas as restrições que afetam uma visualização e fazer o que quisermos com elas ...

Liste seus identificadores ...

for (constraint, _) in someView.constraintsAffectingView{
    print(constraint.identifier ?? "No identifier")
}

Desative-os ...

for (constraint, _) in someView.constraintsAffectingView{
    constraint.isActive = false
}

Ou remova-os inteiramente ...

for (constraint, owningView) in someView.constraintsAffectingView{
    owningView.removeConstraints([constraint])
}

Aproveitar!

Mark A. Donohoe
fonte
0

Rápido

Seguir a extensão UIView removerá todas as restrições de borda de uma visualização:

extension UIView {
    func removeAllConstraints() {
        if let _superview = self.superview {
            self.removeFromSuperview()
            _superview.addSubview(self)
        }
    }
}
Gurjit Singh
fonte
-1

Esta é a maneira de desativar todas as restrições de uma visão específica

 NSLayoutConstraint.deactivate(myView.constraints)
user2501116
fonte
O problema com essa solução é que ela remove apenas as restrições que essa visualização possui (por exemplo, sua largura e altura). Não remove coisas como as restrições principais e superiores.
Senseful