web-dev-qa-db-fra.com

Supprimer toutes les contraintes affectant UIView

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:

  • Grand-père
    • Père
      • Moi
        • Fils
        • Fille
      • Frère
    • Oncle

Maintenant, imaginons que nous créons les contraintes suivantes et les attachons toujours à leur ancêtre commun le plus proche:

  • C0: Moi: même top que Son (propriété de moi)
  • C1: moi: largeur = 100 (propriété de moi)
  • C2: moi: même hauteur que Brother (propriété du père)
  • C3: Moi: même top que Oncle (propriété de grand-père)
  • C4: moi: même chose que grand-père (propriété de grand-père)
  • C5: Frère: même nom que le père (propriété du père)
  • C6: oncle: même à gauche que grand-père (propriété de grand-père)
  • C7: Fils: même chose que fille (propriété de moi)

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.

68
Senseful

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.

41
Senseful

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.


Swift 4 Version

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
    }
}
52
marchinram

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)
    }
}
40
emdog4

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)
    }
}
15
Alexander Volkov

Il existe deux manières de réaliser cela selon Documentation pour les développeurs Apple

1. 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)

2. 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)

Conseils

Utiliser Storyboards ou XIBs 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:

  1. Créer vos vues par programme
  2. Disposez-les et utilisez NSLayoutAnchor
  3. Ajoute chaque contrainte susceptible d'être supprimée ultérieurement à un tableau
  4. Efface-les à chaque fois avant d'appliquer le nouvel état

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

  1. NSLayoutConstraint
  2. IView
14
E-Riddie

Détails

  • Xcode 10.2.1 (10E1001), Swift 5

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()
3
Vasily Bodnarchuk

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. .

2
Darkseal

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];
}
1
Giang

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.

1
Guig

Basé sur les réponses précédentes (Swift 4)

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
}
}
1
eonist

(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 =)

0
BennyTheNerd

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.

0
Tristan