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.constraints
ou 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
- Eu
- Tio
- Pai
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 C7
desnecessariamente.
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 C5
relação com Me
.
Se eu usar grandfather.constraints
, vou conseguir [C3,C4,C6]
. Novamente, não podemos remover todos eles, pois C6
devem permanecer intactos.
A abordagem de força bruta consiste em percorrer cada um dos ancestrais da visualização (incluindo ela mesma) e ver se firstItem
ou secondItem
sã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.
fonte
Respostas:
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 } }
fonte
C7
. No entanto, deve ser fácil de corrigir se você remover[self removeConstraints:self.constraints];
e alterarUIView *superview = self.superview;
paraUIView *superview = self;
.A única solução que encontrei até agora é remover a visualização de sua supervisão:
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.fonte
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) } }
fonte
self.constraints
retorna apenas as restrições queself
possui, não todas as restrições que afetamself
.[C6]
incorretamente e não removeria incorretamente[C0,C1,C2]
.Existem duas maneiras de conseguir isso de acordo com a documentação do desenvolvedor da Apple
1
NSLayoutConstraint.deactivateConstraints
// Declaration class func deactivate(_ constraints: [NSLayoutConstraint]) // Usage NSLayoutConstraint.deactivate(yourView.constraints)
2.
UIView.removeConstraints
(Obsoleto para> = iOS 8.0)// Declaration func removeConstraints(_ constraints: [NSLayoutConstraint])` // Usage yourView.removeConstraints(yourView.constraints)
Dicas
Usar
Storyboard
s ouXIB
s 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 vezesInterface Builder
cria mais problemas do que resolve.Portanto, ao ter um conteúdo muito dinâmico e diferentes estados de visão, eu sugeriria:
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
fonte
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) } }
fonte
if c.firstItem === self
vez de lançaras? UIView
e verificar a igualdade com==
Detalhes
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()
fonte
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.
fonte
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 } }
fonte
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.
fonte
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.😉
fonte
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]; }
fonte
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.
fonte
[C7]
e não removerá incorretamente[C3,C4]
.(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 =)
fonte
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
Arrays
deNSLayoutConstraint
que 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
UIView
ativada 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!
fonte
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) } } }
fonte
Esta é a maneira de desativar todas as restrições de uma visão específica
NSLayoutConstraint.deactivate(myView.constraints)
fonte