web-dev-qa-db-fra.com

Comment "trouver" votre propre contrainte?

Dis que j'ai un UIView, 

 class CleverView: UIView

Dans la classe personnalisée, je veux faire ceci:

func changeWidth() {

  let c = ... find my own layout constraint, for "width"
  c.constant = 70 * Gameinfo.ImportanceOfEnemyFactor
}

De même, je veux pouvoir "trouver" comme ça, la contrainte (ou, je suppose, toutes les contraintes, il pourrait y en avoir plus d'une) attachée à l'un des quatre bords.

Alors, regardez à travers toutes les contraintes qui me sont attachées, et trouvez toutes les contraintes largeur/hauteur, ou même celles qui sont pertinentes pour un bord donné (par exemple, "à gauche").

Des idées?

Il est peut-être intéressant de noter cette question


S'il vous plaît, notez que (évidemment) je demande comment faire cela dynamiquement/par programme.

(Oui, vous pouvez dire "lien vers la contrainte" ou "utiliser un identifiant" - le but principal du contrôle qualité est de savoir comment les trouver à la volée et travailler de manière dynamique.)

Si vous êtes nouveau sur les contraintes, notez que .constraints vous donne simplement les extrémités stockées "là-bas".

16
Fattie

Il y a vraiment deux cas:

  1. Les contraintes concernant la taille d'une vue ou les relations avec les vues descendantes sont enregistrées en soi
  2. Les contraintes entre deux vues sont enregistrées dans l'ancêtre commun le plus bas des vues.

Répéter. Pour les contraintes situées entre deux vues. En fait, iOS les stocke toujours dans le plus petit ancêtre commun. Ainsi, une contrainte d'une vue peut toujours être trouvée en recherchant tous les ancêtres de la vue.

Ainsi, nous devons vérifier la vue elle-même et toutes ses superviews pour s’assurer de la présence de contraintes. Une approche pourrait être:

extension UIView {

    // retrieves all constraints that mention the view
    func getAllConstraints() -> [NSLayoutConstraint] {

        // array will contain self and all superviews
        var views = [self]

        // get all superviews
        var view = self
        while let superview = view.superview {
            views.append(superview)
            view = superview
        }

        // transform views to constraints and filter only those
        // constraints that include the view itself
        return views.flatMap({ $0.constraints }).filter { constraint in
            return constraint.firstItem as? UIView == self ||
                constraint.secondItem as? UIView == self
        }
    }
}

Vous pouvez appliquer toutes sortes de filtres après avoir obtenu toutes les contraintes relatives à une vue, et je suppose que c'est la partie la plus difficile. Quelques exemples:

extension UIView {

    // Example 1: Get all width constraints involving this view
    // We could have multiple constraints involving width, e.g.:
    // - two different width constraints with the exact same value
    // - this view's width equal to another view's width
    // - another view's height equal to this view's width (this view mentioned 2nd)
    func getWidthConstraints() -> [NSLayoutConstraint] {
        return getAllConstraints().filter( {
            ($0.firstAttribute == .width && $0.firstItem as? UIView == self) ||
            ($0.secondAttribute == .width && $0.secondItem as? UIView == self)
        } )
    }

    // Example 2: Change width constraint(s) of this view to a specific value
    // Make sure that we are looking at an equality constraint (not inequality)
    // and that the constraint is not against another view
    func changeWidth(to value: CGFloat) {

        getAllConstraints().filter( {
            $0.firstAttribute == .width &&
                $0.relation == .equal &&
                $0.secondAttribute == .notAnAttribute
        } ).forEach( {$0.constant = value })
    }

    // Example 3: Change leading constraints only where this view is
    // mentioned first. We could also filter leadingMargin, left, or leftMargin
    func changeLeading(to value: CGFloat) {
        getAllConstraints().filter( {
            $0.firstAttribute == .leading &&
                $0.firstItem as? UIView == self
        }).forEach({$0.constant = value})
    }
}

// edit: exemples améliorés et clarifications de leurs explications dans les commentaires

16
stakri

Je suppose que vous pouvez travailler avec contraintes propriété de UIView. constraints renvoie en gros un tableau de contraintes directement assignées à UIView. Il ne sera pas en mesure de vous obtenir les contraintes de superview telles que celles de début, de fin, de haut ou de bas, mais les contraintes de largeur et de hauteur sont gérées par View. Pour les contraintes de superview, vous pouvez parcourir les contraintes de superview. Disons que la vue intelligente a ces contraintes:

 enter image description here

class CleverView: UIView {

    func printSuperViewConstriantsCount() {
        var c = 0
        self.superview?.constraints.forEach({ (constraint) in
            guard constraint.secondItem is CleverView || constraint.firstItem is CleverView else {
                return
            }
            c += 1
            print(constraint.firstAttribute.toString())
        })
        print("superview constraints:\(c)")
    }

    func printSelfConstriantsCount() {
        self.constraints.forEach { (constraint) in
            return print(constraint.firstAttribute.toString())
        }
        print("self constraints:\(self.constraints.count)")
    }
}

Sortie:

haut
de premier plan
traînant
superview contraintes: 3
la taille
contraintes personnelles: 1

Fondamentalement, vous pouvez consulter NSLayoutConstraint class pour obtenir des informations sur une contrainte particulière.

Pour imprimer le nom des contraintes, on peut utiliser cette extension

extension NSLayoutAttribute {
    func toString() -> String {
        switch self {
        case .left:
            return "left"
        case .right:
            return "right"
        case .top:
            return "top"
        case .bottom:
            return "bottom"
        case .leading:
            return "leading"
        case .trailing:
            return "trailing"
        case .width:
            return "width"
        case .height:
            return "height"
        case .centerX:
            return "centerX"
        case .centerY:
            return "centerY"
        case .lastBaseline:
            return "lastBaseline"
        case .firstBaseline:
            return "firstBaseline"
        case .leftMargin:
            return "leftMargin"
        case .rightMargin:
            return "rightMargin"
        case .topMargin:
            return "topMargin"
        case .bottomMargin:
            return "bottomMargin"
        case .leadingMargin:
            return "leadingMargin"
        case .trailingMargin:
            return "trailingMargin"
        case .centerXWithinMargins:
            return "centerXWithinMargins"
        case .centerYWithinMargins:
            return "centerYWithinMargins"
        case .notAnAttribute:
            return "notAnAttribute"
        }
    }
}
9
Puneet Sharma

Peut sauver quelqu'un de la dactylographie .......

Sur la base de la réponse gagnante de Stakri, voici comment obtenir

toutes les contraintes du type "largeur fractionnaire d'une autre vue"

toutes les contraintes du type "largeur de point fixe"

Alors ..

 fileprivate extension UIView {
    func widthAsPointsConstraints()->[NSLayoutConstraint] {}
    func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {}
}

Kode complet ci-dessous. Bien sûr, vous pouvez faire la "hauteur" de la même manière.

Alors, utilisez-les comme ça ...

let cc = someView.widthAsFractionOfAnotherViewConstraints()
for c in cc {
   c.changeToNewConstraintWith(multiplier: 0.25)
}

ou

let cc = someView.widthAsPointsConstraints()
for c in cc {
    c.constant = 150.0
}

Aussi, en bas, j'ai collé dans un code de démonstration simple, exemple de sortie ...

 enter image description here

Voici le kode. V2 ...

fileprivate extension UIView { // experimental

    func allConstraints()->[NSLayoutConstraint] {

        var views = [self]
        var view = self
        while let superview = view.superview {

            views.append(superview)
            view = superview
        }

        return views.flatMap({ $0.constraints }).filter { constraint in
            return constraint.firstItem as? UIView == self ||
                constraint.secondItem as? UIView == self
        }
    }

     func widthAsPointsConstraints()->[NSLayoutConstraint] {

        return self.allConstraints()
         .filter({
            ( $0.firstItem as? UIView == self && $0.secondItem == nil )
         })
         .filter({
            $0.firstAttribute == .width && $0.secondAttribute == .notAnAttribute
         })
    }

    func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {

        func _bothviews(_ c: NSLayoutConstraint)->Bool {
            if c.firstItem == nil { return false }
            if c.secondItem == nil { return false }
            if !c.firstItem!.isKind(of: UIView.self) { return false }
            if !c.secondItem!.isKind(of: UIView.self) { return false }
            return true
        }

        func _ab(_ c: NSLayoutConstraint)->Bool {
            return _bothviews(c)
                && c.firstItem as? UIView == self
                && c.secondItem as? UIView != self
                && c.firstAttribute == .width
        }

        func _ba(_ c: NSLayoutConstraint)->Bool {
            return _bothviews(c)
                && c.firstItem as? UIView != self
                && c.secondItem as? UIView == self
                && c.secondAttribute == .width
        }

        // note that .relation could be anything: and we don't mind that

        return self.allConstraints()
            .filter({ _ab($0) || _ba($0) })
    }
}

extension NSLayoutConstraint {

    // typical routine to "change" multiplier fraction...

    @discardableResult
    func changeToNewConstraintWith(multiplier:CGFloat) -> NSLayoutConstraint {

        //NSLayoutConstraint.deactivate([self])
        self.isActive = false

        let nc = NSLayoutConstraint(
            item: firstItem as Any,
            attribute: firstAttribute,
            relatedBy: relation,
            toItem: secondItem,
            attribute: secondAttribute,
            multiplier: multiplier,
            constant: constant)

        nc.priority = priority
        nc.shouldBeArchived = self.shouldBeArchived
        nc.identifier = self.identifier

        //NSLayoutConstraint.activate([nc])
        nc.isActive = true
        return nc
    }
}

Juste un exemple de démonstration ...

override func viewDidAppear(_ animated: Bool) {

    super.viewDidAppear(animated)

    _teste()

    delay(5) {
        print("changing any 'fraction fo another view' style widths ...\n\n")
        let cc = self.animeHolder.widthAsFractionOfAnotherViewConstraints()
        for c in cc {
            c.changeToNewConstraintWith(multiplier: 0.25)
        }
        self._teste()
    }

    delay(10) {
        print("changing any 'points' style widths ...\n\n")
        let cc = self.animeHolder.widthAsPointsConstraints()
        for c in cc {
            c.constant = 150.0
        }
        self._teste()
    }
}

func _teste() {

    print("\n---- allConstraints")
    for c in animeHolder.allConstraints() {
        print("\n \(c)")
    }
    print("\n---- widthAsPointsConstraints")
    for c in animeHolder.widthAsPointsConstraints() {
        print("\n \(c)\n \(c.multiplier) \(c.constant)")
    }
    print("\n---- widthAsFractionOfAnotherViewConstraints")
    for c in animeHolder.widthAsFractionOfAnotherViewConstraints() {
        print("\n \(c)\n \(c.multiplier) \(c.constant)")
    }
    print("\n----\n")
}
0
Fattie