J'ai un UIView qui se place à l'écran via plusieurs contraintes. Certaines des contraintes appartiennent au superview, d'autres appartiennent à d'autres ancêtres (par exemple, peut-être la propriété view d'un UIViewController).
Je veux supprimer toutes ces anciennes contraintes et les placer dans un nouvel emplacement en utilisant de nouvelles contraintes.
Comment puis-je faire cela sans créer un IBOutlet pour chaque contrainte et avoir à se souvenir de la vue qui possède ladite contrainte?
Pour élaborer, l'approche naïve consisterait à créer un tas d'IBOutlets pour chacune des contraintes, et impliquerait ensuite d'appeler du code tel que:
[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];
Le problème avec ce code est que même dans le cas le plus simple, j'aurais besoin de créer 2 IBOutlets. Pour les mises en page complexes, cela pourrait facilement atteindre 4 ou 8 IBOutlets requis. De plus, je devrais m'assurer que mon appel pour supprimer la contrainte est appelé sur la vue appropriée. Par exemple, imaginez que cela myViewsLeftConstraint
appartient à viewA
. Si j'appelais accidentellement [self.view removeConstraint:self.myViewsLeftConstraint]
, rien ne se passerait.
Remarque: La méthode contraintesAffectingLayoutForAxis semble prometteuse, mais est destinée à des fins de débogage uniquement.
Mise à jour: La plupart des réponses que je reçois accord avec self.constraints
, self.superview.constraints
ou une variante de ceux -ci . Ces solutions ne fonctionneront pas car ces méthodes ne renvoient que les contraintes appartenant à la vue, pas celles affectant la vue.
Pour clarifier le problème avec ces solutions, considérez cette hiérarchie de vues:
- Grand-père
- Père
- Moi
- Fils
- Fille
- Frère
- Moi
- Oncle
- Père
Imaginez maintenant que nous créons les contraintes suivantes et que nous les attachons toujours à leur ancêtre commun le plus proche:
- C0: Me: même haut que Son (appartenant à moi)
- C1: Moi: largeur = 100 (appartenant à Moi)
- C2: Moi: même taille que Brother (propriété du père)
- C3: Moi: même haut que l'oncle (appartenant à grand-père)
- C4: Moi: même gauche que Grandfather (propriété de Grandfather)
- C5: Frère: même gauche que Père (propriété du Père)
- C6: Oncle: même gauche que Grandfather (propriété de Grandfather)
- C7: Fils: même gauche que fille (appartenant à moi)
Imaginez maintenant que nous voulons supprimer toutes les contraintes affectant Me
. Toute solution appropriée doit supprimer [C0,C1,C2,C3,C4]
et rien d'autre.
Si j'utilise self.constraints
(où le moi est moi), j'obtiendrai [C0,C1,C7]
, puisque ce sont les seules contraintes que je possède. De toute évidence, il ne suffirait pas de le supprimer car il manque [C2,C3,C4]
. En outre, il supprime C7
inutilement.
Si j'utilise self.superview.constraints
(où le moi est moi), j'obtiendrai [C2,C5]
, puisque ce sont les contraintes détenues par Père. De toute évidence, nous ne pouvons pas supprimer tous ces éléments car cela C5
n'a aucun rapport avec Me
.
Si j'utilise grandfather.constraints
, j'obtiendrai [C3,C4,C6]
. Encore une fois, nous ne pouvons pas supprimer tous ces éléments car ils C6
devraient rester intacts.
L'approche par force brute consiste à boucler sur chacun des ancêtres de la vue (y compris lui-même), et à voir si firstItem
ou secondItem
sont la vue elle-même; si tel est le cas, supprimez cette contrainte. Cela conduira à une solution correcte, à un retour [C0,C1,C2,C3,C4]
, et uniquement à ces contraintes.
Cependant, j'espère qu'il existe une solution plus élégante que de devoir parcourir toute la liste des ancêtres.
Réponses:
Cette approche a fonctionné pour moi:
@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
Une fois l'exécution terminée, votre vue reste où elle était car elle crée des contraintes de redimensionnement automatique. Lorsque je ne fais pas cela, la vue disparaît généralement. De plus, il ne supprime pas seulement les contraintes de superview, mais il traverse tout en haut car il peut y avoir des contraintes qui l'affectent dans les vues ancêtres.
Version 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 } }
la source
C7
. Cependant, il devrait être facile à réparer si vous supprimez[self removeConstraints:self.constraints];
et passezUIView *superview = self.superview;
àUIView *superview = self;
.La seule solution que j'ai trouvée jusqu'à présent est de supprimer la vue de sa supervision:
Il semble que cela supprime toutes les contraintes affectant sa disposition et est prêt à être ajouté à un superview et à avoir de nouvelles contraintes attachées. Cependant, il supprimera également de manière incorrecte toutes les sous-vues de la hiérarchie et s'en débarrassera de
[C7]
manière incorrecte.la source
Vous pouvez supprimer toutes les contraintes d'une vue en procédant comme suit:
self.removeConstraints(self.constraints)
MODIFIER: pour supprimer les contraintes de toutes les sous-vues, utilisez l'extension suivante dans Swift:
extension UIView { func clearConstraints() { for subview in self.subviews { subview.clearConstraints() } self.removeConstraints(self.constraints) } }
la source
self.constraints
ne renvoie que les contraintes quiself
possèdent, pas toutes les contraintes qui affectentself
.[C6]
incorrectement et ne supprimerait pas correctement[C0,C1,C2]
.Il existe deux façons d'y parvenir selon la documentation du développeur Apple
1.
NSLayoutConstraint.deactivateConstraints
// Declaration class func deactivate(_ constraints: [NSLayoutConstraint]) // Usage NSLayoutConstraint.deactivate(yourView.constraints)
2.
UIView.removeConstraints
(obsolète pour> = iOS 8.0)// Declaration func removeConstraints(_ constraints: [NSLayoutConstraint])` // Usage yourView.removeConstraints(yourView.constraints)
Conseils
L'utilisation de
Storyboard
s ouXIB
s peut être une telle difficulté à configurer les contraintes comme mentionné dans votre scénario, vous devez créer des IBOutlets pour chacun de ceux que vous souhaitez supprimer. Même ainsi, la plupart du temps, celaInterface Builder
crée plus de problèmes qu'il n'en résout.Par conséquent, lorsque vous avez un contenu très dynamique et différents états de vue, je suggère:
Code simple
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() }
Références
la source
Dans 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) } }
la source
if c.firstItem === self
au lieu de lanceras? UIView
et de vérifier l'égalité avec==
Détails
Solution
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) } } }
Usage
print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)") view.deactivateAllConstraints()
la source
Une solution Swift:
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 } } }
Il est important de passer en revue tous les parents, car les contraintes entre deux éléments sont détenues par les ancêtres communs, il ne suffit donc pas d'effacer le superview comme détaillé dans cette réponse , et vous pourriez finir par avoir une mauvaise surprise plus tard.
la source
Basé sur les réponses précédentes (swift 4)
Vous pouvez utiliser des contraintes immédiates lorsque vous ne souhaitez pas analyser des hiérarchies entières.
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 } }
la source
J'utilise la méthode suivante pour supprimer toutes les contraintes d'une vue:
fichier .h:
+ (void)RemoveContraintsFromView:(UIView*)view removeParentConstraints:(bool)parent removeChildConstraints:(bool)child;
Fichier .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]]; } }
Vous pouvez également lire cet article sur mon blog pour mieux comprendre comment cela fonctionne derrière le capot.
Si vous avez besoin d'un contrôle plus granulaire, je vous conseillerais fortement de passer à Masonry , une classe de cadre puissante que vous pouvez utiliser chaque fois que vous avez besoin de gérer correctement les contraintes par programme.
la source
L'approche la plus simple et efficace consiste à supprimer la vue de superView et à l'ajouter à nouveau en tant que sous-vue. cela provoque la suppression automatique de toutes les contraintes de sous-vue.
la source
Avec objectifC
[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]; }
la source
Vous pouvez utiliser quelque chose comme ceci:
[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];
Fondamentalement, cela énumère toutes les contraintes sur la vue supervisée de viewA et supprime toutes les contraintes liées à viewA.
Ensuite, la deuxième partie supprime les contraintes de viewA en utilisant le tableau de contraintes de viewA.
la source
[C7]
et ne supprimera pas correctement[C3,C4]
.(Au 31 juillet 2017)
SWIFT 3
self.yourCustomView.removeFromSuperview() self.yourCustomViewParentView.addSubview(self.yourCustomView)
Objectif c
[self.yourCustomView removeFromSuperview]; [self.yourCustomViewParentView addSubview:self.yourCustomView];
Il s'agit du moyen le plus simple de supprimer rapidement toutes les contraintes qui existent sur un UIView. Assurez-vous simplement de rajouter l'UIView avec ses nouvelles contraintes ou un nouveau cadre après =)
la source
Utilisation d'une séquence réutilisable
J'ai décidé d'aborder cela d'une manière plus «réutilisable». Étant donné que la recherche de toutes les contraintes affectant une vue est la base de tout ce qui précède, j'ai décidé d'implémenter une séquence personnalisée qui les renvoie toutes pour moi, ainsi que les vues propriétaires.
La première chose à faire est de définir une extension sur
Arrays
deNSLayoutConstraint
qui renvoie tous les éléments affectant une vue spécifique.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 } } }
Nous utilisons ensuite cette extension dans une séquence personnalisée qui renvoie toutes les contraintes affectant cette vue, ainsi que les vues qui les possèdent réellement (qui peuvent être n'importe où dans la hiérarchie des vues)
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) } } } }
Enfin, nous déclarons une extension sur
UIView
pour exposer toutes les contraintes qui l'affectent dans une propriété simple à laquelle vous pouvez accéder avec une syntaxe simple pour chaque.extension UIView { var constraintsAffectingView:AllConstraintsSequence { return AllConstraintsSequence(view:self) } }
Maintenant, nous pouvons itérer toutes les contraintes affectant une vue et faire ce que nous voulons avec elles ...
Lister leurs identifiants ...
for (constraint, _) in someView.constraintsAffectingView{ print(constraint.identifier ?? "No identifier") }
Désactivez-les ...
for (constraint, _) in someView.constraintsAffectingView{ constraint.isActive = false }
Ou supprimez-les entièrement ...
for (constraint, owningView) in someView.constraintsAffectingView{ owningView.removeConstraints([constraint]) }
Prendre plaisir!
la source
Rapide
L'extension UIView suivante supprimera toutes les contraintes Edge d'une vue:
extension UIView { func removeAllConstraints() { if let _superview = self.superview { self.removeFromSuperview() _superview.addSubview(self) } } }
la source
C'est la manière de désactiver toutes les contraintes d'une vue spécifique
NSLayoutConstraint.deactivate(myView.constraints)
la source