Как обновить 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()
}