Как обновить Swift Layout Anchors?

Попытка найти решение для обновления нескольких ограничений для нескольких элементов пользовательского интерфейса на событии. Я видел несколько примеров деактивации, внесения изменений, а затем реактивации ограничений. Этот метод кажется непрактичным для 24 якорей, с которыми я работаю.

Один из моих наборов изменений:

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

5 ответов

Решение

Вы пытались сохранить соответствующие ограничения, которые вы создали, используя привязки макета к свойствам, а затем просто изменив константу? Например

var ticketTop : NSLayoutConstraint?

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

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

Возможно, более элегантный

В зависимости от вашего вкуса написания расширений, здесь возможно более элегантный подход, который не использует свойства, но вместо этого создает методы расширения на NSLayoutAnchor а также UIView чтобы помочь в более кратком использовании.

Сначала вы написали бы расширение на NSLayoutAnchor как это:

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

Это расширение позволяет вам установить идентификатор ограничения в том же вызове метода, который создает его из привязки. Обратите внимание, что документация Apple подразумевает, что якоря XAxis (левый, правый, ведущий и т. Д.) Не позволят вам создать ограничение с помощью якорей YAxis (верхний, нижний и т. Д.), Но я не считаю, что это действительно так. Если вам нужна такая проверка компилятора, вам нужно написать отдельные расширения для NSLayoutXAxisAnchor, NSLayoutYAxisAnchor, а также NSLayoutDimension (для ограничений ширины и высоты), которые обеспечивают требование к типу привязки к одной оси.

Далее вы бы написали расширение на UIView чтобы получить ограничение по идентификатору:

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

С этими расширениями ваш код становится:

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

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

Обратите внимание, что использование констант или перечислений вместо имен магических строк для идентификаторов было бы улучшением по сравнению с вышеупомянутым, но я держу этот ответ кратким и сфокусированным.

Вы можете перебирать ограничения вида и проверять соответствие элементов и якорей. Помните, что ограничение будет на суперпредставлении представления, если это не ограничение размера. Я написал некоторый вспомогательный код, который позволит вам найти все ограничения для одного якоря представления.

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

}

Позиционирование одного вида

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

    }
}


использование

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

Полезное расширение

      extension UIView {

    //Note: Use `leadingAnchor and trailingAnchor` instead of `leftAnchor and rightAnchor`, it is going to help in RTL
    func addConstraint(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, paddingTop: CGFloat? = nil, paddingLeft: CGFloat? = nil, paddingBottom: CGFloat? = nil, paddingRight: CGFloat? = nil, width: CGFloat? = nil, height: CGFloat? = nil) {
        translatesAutoresizingMaskIntoConstraints = false
        //top constraint
        if let top = top {
            self.topAnchor.constraint(equalTo: top, constant: paddingTop ?? 0).isActive = true
        }
    
        // leading/left constraint
        if let leading = leading {
            self.leadingAnchor.constraint(equalTo: leading, constant: paddingLeft ?? 0).isActive = true
        }

        //  bottom constraint
        if let bottom = bottom {
            self.bottomAnchor.constraint(equalTo: bottom, constant: -(paddingBottom ?? 0) ).isActive = true
        }

        // trailing/right constraint
        if let trailing = trailing {
            self.trailingAnchor.constraint(equalTo: trailing, constant: -(paddingRight ?? 0)).isActive = true
        }

        //width constraint
        if let width = width {
            widthAnchor.constraint(equalToConstant: width).isActive = true
        }

        //height constraint
        if let height = height {
            heightAnchor.constraint(equalToConstant: height).isActive = true
        }
    }
}

Как использовать

       let myView = UIView()
 self.view.addSubview(myView)
 myView.backgroundColor = .yellow
 myView.addConstraint(top: self.view.safeAreaLayoutGuide.topAnchor, leading: self.view.safeAreaLayoutGuide.leadingAnchor, bottom: self.view.safeAreaLayoutGuide.bottomAnchor, trailing: self.view.safeAreaLayoutGuide.trailingAnchor, paddingTop: 100)

Я нашел другое решение. Если вы хотите изменить существующее ограничение, добавленное через Interface Builder, вам действительно необходимо перебрать ограничения супервизора. По крайней мере, это верно, когда вы пытаетесь изменить постоянное значение ограничений выравнивания.

В приведенном ниже примере показано выравнивание по нижнему краю, но я предполагаю, что тот же код будет работать для выравнивания вперед / назад / вверх.

    private func bottomConstraint(view: UIView) -> NSLayoutConstraint {
        guard let superview = view.superview else {
            return NSLayoutConstraint()
        }

        for constraint in superview.constraints {
            for bottom in [NSLayoutConstraint.Attribute.bottom, NSLayoutConstraint.Attribute.bottomMargin] {
                if constraint.firstAttribute == bottom && constraint.isActive && view == constraint.secondItem as? UIView {
                    return constraint
                }

                if constraint.secondAttribute == bottom && constraint.isActive && view == constraint.firstItem as? UIView {
                    return constraint
                }
            }
        }

        return NSLayoutConstraint()
    }
Другие вопросы по тегам