Supprimer toutes les contraintes affectant un UIView


87

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 myViewsLeftConstraintappartient à 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.constraintsou 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
    • Oncle

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

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 C5n'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 C6devraient 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 firstItemou secondItemsont 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.


Que diriez-vous de mettre un identifiant à toutes les contraintes que vous souhaitez supprimer? De cette façon, vous n'avez pas besoin de leur garder une prise.
nsuinteger

Réponses:


69

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

3
Cela devrait être la réponse officielle.
Jason Crocker

pourquoi avez-vous self.translatesAutoresizingMaskIntoConstraints = YES; ? vous n'en voulez littéralement jamais pour les vues que vous configurez avec des contraintes?
lensovet

4
Je supprime les contraintes, donc je veux utiliser les contraintes de redimensionnement automatique après avoir supprimé les autres contraintes pour les maintenir en place, sans cette ligne dans mon code, la vue disparaîtrait
marchinram

J'avais besoin de consigner toutes les contraintes sur une vue à des fins de débogage et j'ai pu modifier légèrement cette réponse pour ce faire. +1
chiliNUT

Cela supprimerait incorrectement C7. Cependant, il devrait être facile à réparer si vous supprimez [self removeConstraints:self.constraints];et passez UIView *superview = self.superview;à UIView *superview = self;.
Senseful

49

La seule solution que j'ai trouvée jusqu'à présent est de supprimer la vue de sa supervision:

[view removeFromSuperview]

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.


Vous pouvez utiliser les propriétés firstItem et secondItem des contraintes pour vérifier si elles s'appliquent à votre vue et parcourir la hiérarchie des vues pour les trouver toutes. Je pense cependant que supprimer et réajouter est une meilleure solution. Je serais intéressé de voir votre cas d'utilisation car il semble que ce soit une très mauvaise chose à faire. Vos contraintes peuvent facilement devenir complètement invalides car d'autres vues peuvent s'appuyer sur ces contraintes pour se présenter correctement.
bjtitus

@bjtitus: Bon point. Cependant, dans ce cas précis, je repositionne un point de vue qui n'en dépend pas. Il utilise d'autres vues pour savoir où placer, et aucune vue ne l'utilise pour savoir où placer.
Senseful

Pas la seule solution, mais très simple. Dans mon cas d'utilisation, je viens de supprimer la vue de superview, puis de la rajouter plus tard.
Marián Černý

48

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

22
Le problème avec cette solution est qu'elle supprime uniquement les contraintes que cette vue possède (par exemple sa largeur et sa hauteur). Il ne supprime pas des éléments tels que les contraintes principales et supérieures.
Senseful

2
Je n'ai pas vérifié votre commentaire ci-dessus. Cependant, je crois que cela fonctionnerait de la même manière ou mieux que ci-dessus, [NSLayoutConstraint deactivateConstraints: self.constraints];
emdog4

2
Cela souffrirait du même problème que la solution originale. self.constraintsne renvoie que les contraintes qui selfpossèdent, pas toutes les contraintes qui affectent self.
Senseful

1
Je comprends ce que vous dites maintenant. Dans ma réponse , je supprime les contraintes du contentView de UITableViewCell. La méthode updateConstraints ajoute alors des contraintes pour toutes les sous-vues et réinitialise la disposition. Dans mon premier commentaire ci-dessus, j'aurais dû taper self.view.constraints. Self.view et self.contentView sont tous deux en haut de la hiérarchie des vues. La définition de translatesAutoresizingMasksToConstraints = YES sur self.view ou self.contentView est une mauvaise idée. ;-). Je n'aime pas les appels à addSubview: dans ma méthode updateConstraints, supprimer la vue de la hiérarchie semble inutile imo.
emdog4

Je viens de mettre à jour la question pour montrer pourquoi une solution comme celle-ci ne fonctionnera pas pour ce que je cherche à faire. En supposant que vous soyez dans un UITableViewCell, l'équivalent de contentView est Grandfather. Par conséquent, votre solution supprimerait [C6]incorrectement et ne supprimerait pas correctement [C0,C1,C2].
Senseful

20

Il existe deux façons d'y parvenir selon la documentation du développeur 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. L'effet de cette méthode est le même que la définition de la propriété isActive de chaque contrainte sur false. En général, cette méthode est plus efficace que de désactiver chaque contrainte individuellement.

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

L'utilisation de Storyboards ou XIBs 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, cela Interface Buildercré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:

  1. Créer vos vues par programmation
  2. Disposez- les et utilisez NSLayoutAnchor
  3. Ajouter chaque contrainte susceptible d'être supprimée ultérieurement à un tableau
  4. Effacez-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. UIView

2
Faites attention. Les vues qui spécifient une taille de contenu intrinsèque verront NSContentSizeLayoutConstraint ajouté automatiquement par iOS. Le supprimer en accédant à la propriété normale yourView.constraints interrompra la mise en page automatique sur ces vues.
TigerCoding

Le problème avec cette solution est qu'elle supprime uniquement les contraintes que cette vue possède (par exemple sa largeur et sa hauteur). Il ne supprime pas des éléments tels que les contraintes principales et supérieures.
Senseful

Ouais, ce que Senseful a dit. Bien que ces méthodes existent, elles ne résolvent pas à elles seules le problème que la question pose réellement.
GSnyder

18

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

Cela plantera si votre vue n'a pas de supervision dans 'superview!'. Veuillez envelopper la partie dans un 'if let superview = superview' :)
Bersaelor

1
Cela ne supprimera pas toutes les contraintes. Les contraintes prennent le parent le plus proche des 2 vues affectées, donc si vous créez une contrainte qui ne partage pas la supervision de cette vue, cette contrainte ne sera pas supprimée ...
vrwim

2
peut-être plus correct serait de comparer l'identité if c.firstItem === selfau lieu de lancer as? UIViewet de vérifier l'égalité avec==
user1244109

Ne devrait probablement pas utiliser removeConstraints: from apple doc: // La méthode removeConstraints sera obsolète dans une prochaine version et devrait être évitée. À la place, utilisez + [NSLayoutConstraint deactivateConstraints:].
eonist

@eonist la réponse est il y a 3 ans. Au lieu de critiques, vous devriez le modifier.
Alexander Volkov

5

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

1
C'est plutôt sympa.
eonist

2

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.


2

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

2

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.


2
Et les frères et sœurs?
zrslv

2

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.


1

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

0

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.


1
Le problème avec cette solution est qu'elle supprime uniquement les contraintes appartenant au superview. Il ne supprime pas toutes les contraintes affectant la vue. (Par exemple, vous auriez pu tout aussi facilement configurer des contraintes sur le superview de superview, qui ne seraient pas supprimées en utilisant cette solution.)
Senseful

Je viens de mettre à jour la question pour montrer pourquoi cette solution ne fonctionnera pas. C'est le plus proche de l'exactitude, car il est le plus proche de la solution de force brute que j'ai mentionnée ci-dessus. Cependant, cette solution supprimera incorrectement [C7]et ne supprimera pas correctement [C3,C4].
Senseful

vous pouvez concevoir la méthode pour remonter la hiérarchie. Mais cette solution est suffisante, car vous devez de toute façon ajouter des contraintes au-dessus de la supervision immédiate. IMO
eonist

0

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


0

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 Arraysde NSLayoutConstraintqui 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 UIViewpour 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!


0

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

-1

C'est la manière de désactiver toutes les contraintes d'une vue spécifique

 NSLayoutConstraint.deactivate(myView.constraints)

Le problème avec cette solution est qu'elle supprime uniquement les contraintes que cette vue possède (par exemple sa largeur et sa hauteur). Il ne supprime pas des éléments tels que les contraintes principales et supérieures.
Senseful
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.