web-dev-qa-db-fra.com

Comment mettre à jour les ancres Swift Layout?

Essayer de trouver une solution pour mettre à jour plusieurs contraintes pour plusieurs éléments d'interface utilisateur sur un événement. J'ai vu quelques exemples de désactivation, de modification, puis de réactivation de contraintes. Cette méthode semble peu pratique pour les 24 ancres avec lesquelles je travaille.

Un de mes ensembles de modifications:

ticketContainer.translatesAutoresizingMaskIntoConstraints = false
ticketContainer.topAnchor.constraintEqualToAnchor(self.topAnchor).active = true
ticketContainer.leftAnchor.constraintEqualToAnchor(self.rightAnchor, constant: 20).active = true
ticketContainer.widthAnchor.constraintEqualToConstant(200.0).active = true

ticketContainer.leftAnchor.constraintEqualToAnchor(self.leftAnchor, constant: 20).active = true
ticketContainer.widthAnchor.constraintEqualToConstant(100.0).active = true
9
Justin

Avez-vous essayé d'enregistrer les contraintes pertinentes créées à l'aide des ancres de présentation dans les propriétés, puis de modifier simplement la constante? Par exemple.

var ticketTop : NSLayoutConstraint?

func setup() {
    ticketTop = ticketContainer.topAnchor.constraintEqualToAnchor(self.topAnchor, constant:100)
    ticketTop.active = true
}

func update() {
    ticketTop?.constant = 25
}

Peut-être plus élégant

Selon votre goût pour les extensions d'écriture, voici une approche peut-être plus élégante qui n'utilise pas de propriétés, mais crée plutôt des méthodes d'extension sur NSLayoutAnchor et UIView pour aider à une utilisation plus succincte.

Commencez par écrire une extension sur NSLayoutAnchor comme ceci:

extension NSLayoutAnchor {
    func constraintEqualToAnchor(anchor: NSLayoutAnchor!, constant:CGFloat, identifier:String) -> NSLayoutConstraint! {
        let constraint = self.constraintEqualToAnchor(anchor, constant:constant)
        constraint.identifier = identifier
        return constraint
    }
}

Cette extension vous permet de définir un identifiant sur la contrainte dans le même appel de méthode qui le crée à partir d'une ancre. Notez que la documentation Apple implique que les ancres XAxis (gauche, droite, interligne, etc.) ne vous permettent pas de créer une contrainte avec les ancres YAxis (haut, bas, etc.), mais je ne remarque pas que cela soit vrai. Si vous souhaitez ce type de vérification du compilateur, vous devez écrire des extensions distinctes pour NSLayoutXAxisAnchor, NSLayoutYAxisAnchor et NSLayoutDimension (pour les contraintes de largeur et de hauteur) qui appliquent l'exigence de type d'ancrage même axe. 

Ensuite, vous écririez une extension sur UIView pour obtenir une contrainte par identifiant:

extension UIView {
    func constraint(withIdentifier:String) -> NSLayoutConstraint? {
        return self.constraints.filter{ $0.identifier == withIdentifier }.first
    }
}

Avec ces extensions en place, votre code devient:

func setup() {
    ticketContainer.topAnchor.constraintEqualToAnchor(self.topAnchor, constant:100, identifier:"ticketTop").active = true
}

func update() {
    self.constraint(withIdentifier:"ticketTop")?.constant = 25
}

Notez que l'utilisation de constantes ou d'une énumération au lieu de noms de chaînes magiques pour les identificateurs constituerait une amélioration par rapport à ce qui précède, mais je garde cette réponse brève et précise.

39
Daniel Hall

Vous pouvez parcourir les contraintes d'une vue et rechercher des éléments et des ancres correspondants. N'oubliez pas que la contrainte sera sur la vue d'ensemble de la vue, sauf s'il s'agit d'une contrainte de dimension. J'ai écrit un code d'assistance qui vous permettra de trouver toutes les contraintes sur une ancre d'une vue.

import UIKit

class ViewController: UIViewController {

    let label = UILabel()
    let imageView = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "Constraint Finder"

        label.translatesAutoresizingMaskIntoConstraints = false
        imageView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        view.addSubview(imageView)

        label.topAnchor.constraint(equalTo: view.topAnchor, constant: 30).isActive = true
        label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
        label.widthAnchor.constraint(greaterThanOrEqualToConstant: 50).isActive = true

        imageView.topAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
        imageView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 60).isActive = true
        imageView.widthAnchor.constraint(equalTo: label.widthAnchor).isActive = true
        imageView.heightAnchor.constraint(equalToConstant: 70).isActive = true

        print("Label's top achor constraints: \(label.constraints(on: label.topAnchor))")
        print("Label's width achor constraints: \(label.constraints(on: label.widthAnchor))")
        print("ImageView's width achor constraints: \(imageView.constraints(on: imageView.widthAnchor))")
    }

}

public extension UIView {

    public func constraints(on anchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
        guard let superview = superview else { return [] }
        return superview.constraints.filtered(view: self, anchor: anchor)
    }

    public func constraints(on anchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
        guard let superview = superview else { return [] }
        return superview.constraints.filtered(view: self, anchor: anchor)
    }

    public func constraints(on anchor: NSLayoutDimension) -> [NSLayoutConstraint] {
        guard let superview = superview else { return [] }
        return constraints.filtered(view: self, anchor: anchor) + superview.constraints.filtered(view: self, anchor: anchor)
    }

}

extension NSLayoutConstraint {

    func matches(view: UIView, anchor: NSLayoutYAxisAnchor) -> Bool {
        if let firstView = firstItem as? UIView, firstView == view && firstAnchor == anchor {
            return true
        }
        if let secondView = secondItem as? UIView, secondView == view && secondAnchor == anchor {
            return true
        }
        return false
    }

    func matches(view: UIView, anchor: NSLayoutXAxisAnchor) -> Bool {
        if let firstView = firstItem as? UIView, firstView == view && firstAnchor == anchor {
            return true
        }
        if let secondView = secondItem as? UIView, secondView == view && secondAnchor == anchor {
            return true
        }
        return false
    }

    func matches(view: UIView, anchor: NSLayoutDimension) -> Bool {
        if let firstView = firstItem as? UIView, firstView == view && firstAnchor == anchor {
            return true
        }
        if let secondView = secondItem as? UIView, secondView == view && secondAnchor == anchor {
            return true
        }
        return false
    }
}

extension Array where Element == NSLayoutConstraint {

    func filtered(view: UIView, anchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
        return filter { constraint in
            constraint.matches(view: view, anchor: anchor)
        }
    }
    func filtered(view: UIView, anchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
        return filter { constraint in
            constraint.matches(view: view, anchor: anchor)
        }
    }
    func filtered(view: UIView, anchor: NSLayoutDimension) -> [NSLayoutConstraint] {
        return filter { constraint in
            constraint.matches(view: view, anchor: anchor)
        }
    }

}
4
eg.rudolph

Positionnement vue unique

extension UIView {
    func add(view: UIView, left: CGFloat, right: CGFloat, top: CGFloat, bottom: CGFloat) {

        view.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(view)

        view.leftAnchor.constraint(equalTo: self.leftAnchor, constant: left).isActive = true
        view.rightAnchor.constraint(equalTo: self.rightAnchor, constant: right).isActive = true

        view.topAnchor.constraint(equalTo: self.topAnchor, constant: top).isActive = true
        view.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: bottom).isActive = true

    }
}


Usage

headerView.add(view: headerLabel, left: 20, right: 0, top: 0, bottom: 0)
0
Justin