J'ai un UIView qui est placé sur l'écran via plusieurs contraintes. Certaines des contraintes appartiennent à Superview, d'autres à d'autres ancêtres (par exemple, la propriété view d'un UIViewController).
Je souhaite 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 à me rappeler quelle vue possède cette contrainte?
Pour élaborer, l’approche naïve consisterait à créer un groupe d’IBOutlets pour chacune des contraintes et impliquerait alors un code appelant 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, il me faudrait créer 2 IBOutlets. Pour les configurations complexes, cela peut facilement atteindre 4 ou 8 IBOutlets requis. De plus, je devrais m'assurer que mon appel à supprimer la contrainte est appelé sur la vue appropriée. Par exemple, imaginons que myViewsLeftConstraint
appartient à viewA
. Si je devais appeler accidentellement [self.view removeConstraint:self.myViewsLeftConstraint]
, rien ne se passerait.
Remarque: La méthode contraintesAffectingLayoutForAxis semble prometteuse, mais elle est destinée uniquement à des fins de débogage.
Mise à jour: La plupart des réponses que je reçois traitent 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 possédées par la vue, pas celles affectant la vue.
Pour clarifier le problème avec ces solutions, considérons cette hiérarchie de vues:
Maintenant, imaginons que nous créons les contraintes suivantes et les attachons toujours à leur ancêtre commun le plus proche:
Maintenant, imaginons que nous voulions supprimer toutes les contraintes affectant Me
. Toute solution appropriée devrait supprimer [C0,C1,C2,C3,C4]
et rien d'autre.
Si j'utilise self.constraints
(où le moi est Moi), je vais obtenir [C0,C1,C7]
, puisque ce sont les seules contraintes que je possède. Évidemment, cela ne suffirait pas pour supprimer cela car il manque [C2,C3,C4]
. En outre, il supprime C7
inutilement.
Si j'utilise self.superview.constraints
(où le moi est Moi), je vais obtenir [C2,C5]
, puisque ce sont les contraintes appartenant à Père. Évidemment, nous ne pouvons pas supprimer tous ceux-ci puisque C5
n'a aucun lien avec Me
.
Si j'utilise grandfather.constraints
, J'aurai [C3,C4,C6]
. Encore une fois, nous ne pouvons pas supprimer tous ces éléments car C6
devrait rester intact.
La méthode de la force brute consiste à parcourir chacun des ancêtres de la vue (y compris lui-même), et à voir si firstItem
ou secondItem
est la vue elle-même; si tel est le cas, supprimez cette contrainte. Cela conduira à une solution correcte, en retournant [C0,C1,C2,C3,C4]
, et seulement ces contraintes.
Cependant, j'espère qu'il existe une solution plus élégante que d'avoir à parcourir la liste complète des ancêtres.
La seule solution que j'ai trouvée jusqu'à présent est de supprimer la vue de son aperçu:
[view removeFromSuperview]
On dirait que cela supprime toutes les contraintes qui affectent sa mise en page et qu'il est prêt à être ajouté à un aperçu et qu'il comporte de nouvelles contraintes. Cependant, les sous-vues de la hiérarchie seront également incorrectement supprimées et [C7]
incorrectement.
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 que vous avez terminé, votre vue reste telle quelle car elle crée des contraintes de redimensionnement automatique. Lorsque je ne le fais pas, la vue disparaît généralement. De plus, il ne supprime pas simplement les contraintes de superview, mais le traverse complètement, car il peut exister des contraintes l’affectant dans les vues ancêtres.
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
}
}
Vous pouvez supprimer toutes les contraintes dans une vue en procédant comme suit:
[_cell.contentView removeConstraints:_cell.contentView.constraints];
EDIT: 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)
}
}
En rapide:
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)
}
}
Il existe deux manières de réaliser cela selon Documentation pour les développeurs Apple
NSLayoutConstraint.deactivateConstraints
Il s'agit d'une méthode pratique qui permet de désactiver facilement un ensemble de contraintes en un seul appel. Cette méthode a le même effet que de définir la propriété isActive de chaque contrainte sur false. En règle générale, cette méthode est plus efficace que la désactivation individuelle de chaque contrainte.
// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])
// Usage
NSLayoutConstraint.deactivate(yourView.constraints)
UIView.removeConstraints
(Obsolète pour> = iOS 8.0)Lors du développement pour iOS 8.0 ou version ultérieure, utilisez la méthode deactivateConstraints: de la classe NSLayoutConstraint au lieu d’appeler directement la méthode removeConstraints:. La méthode deactivateConstraints: supprime automatiquement les contraintes des vues correctes.
// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])`
// Usage
yourView.removeConstraints(yourView.constraints)
Utiliser Storyboard
s ou XIB
s peut s'avérer très difficile à configurer les contraintes mentionnées dans votre scénario. Vous devez créer des IBOutlets pour chacun de ceux que vous souhaitez supprimer. Malgré tout, la plupart du temps Interface 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 la 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()
}
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) }
}
}
print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()
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 recommanderais fortement de passer à maçonnerie , une classe de structure puissante que vous pouvez utiliser chaque fois que vous devez gérer correctement les contraintes par programme. .
Avec objectiveC
[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];
}
A 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 ancêtres communs tiennent les contraintes entre deux éléments. Il suffit donc de clarifier la vue d'ensemble comme indiqué dans cette réponse n'est pas assez bon et vous pourriez finir par avoir mauvaise surprise plus tard.
Vous pouvez utiliser immediateConstraints 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
}
}
(Au 31 juillet 2017)
Swift
self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)
Objectif C
[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];
C'est le moyen le plus simple de supprimer rapidement toutes les contraintes existant sur UIView. Assurez-vous simplement d’ajouter UIView à ses nouvelles contraintes ou son nouveau cadre =)
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, ceci énumère toutes les contraintes sur la vue d'ensemble 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.