Удалите все ограничения, влияющие на UIView

У меня есть UIView, который помещается на экран с помощью нескольких ограничений. Некоторые из ограничений принадлежат супервину, другие принадлежат другим предкам (например, возможно, свойство view UIViewController).

Я хочу удалить все эти старые ограничения и поместить его где-то новым, используя новые ограничения.

Как я могу это сделать, не создавая IBOutlet для каждого отдельного ограничения и не помню, какое представление принадлежит указанному ограничению?

Чтобы разработать, наивный подход заключался бы в создании группы IBOutlets для каждого из ограничений и затем включал бы код вызова, например:

[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];

Проблема с этим кодом заключается в том, что даже в простейшем случае мне нужно было бы создать 2 IBOutlets. Для сложных макетов это может легко достичь 4 или 8 требуемых IBOutlets. Кроме того, мне нужно будет убедиться, что мой вызов для удаления ограничения вызывается в правильном представлении. Например, представьте, что myViewsLeftConstraint принадлежит viewA. Если бы я случайно вызвал [self.view removeConstraint:self.myViewsLeftConstraint], ничего не произошло.

Примечание. Метод constraintsAffectingLayoutForAxis выглядит многообещающим, но предназначен только для целей отладки.


Обновление:. Многие ответы, которые я получаю, имеют дело с self.constraints, self.superview.constraints или некоторыми вариантами. Эти решения не будут работать, поскольку эти методы возвращают только ограничения, принадлежащие представлению, а не те, которые влияют на представление.

Чтобы прояснить проблему с этими решениями, рассмотрите эту иерархию представлений:

  • Дед
    • Отец
      • Me
        • Сын
        • Дочь
      • Brother
    • Дядя

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

  • C0: Me: тот же самый верх, как Сын (принадлежит мне)
  • C1: Me: width = 100 (принадлежит мне)
  • C2: Me: та же высота, что и брат (принадлежит отцу)
  • C3: Me: тот же самый верх, что и дядя (принадлежащий деду).
  • C4: Я: тот же самый, что и дедушка (принадлежащий деду).
  • C5: Брат: тот же самый, что и Отец (принадлежит отцу)
  • C6: Дядя: тот же самый, что и дед (принадлежащий деду).
  • C7: Сын: тот же, что и Дочь (принадлежит мне).

Теперь представьте, что мы хотим удалить все ограничения, влияющие на Me. Любое правильное решение должно удалить [C0,C1,C2,C3,C4] и ничего больше.

Если я использую self.constraints (где self is Me), я получу [C0,C1,C7], так как это единственные ограничения, которыми владеет Me. Очевидно, этого было бы недостаточно, чтобы удалить это, поскольку отсутствует [C2,C3,C4]. Кроме того, он ненужно удаляет C7.

Если я использую self.superview.constraints (где self is Me), я получу [C2,C5], так как это ограничения, принадлежащие Отцу. Очевидно, мы не можем удалить все это, так как C5 полностью не связано с Me.

Если я использую grandfather.constraints, я получу [C3,C4,C6]. Опять же, мы не можем удалить все из них, так как C6 должен оставаться неповрежденным.

Подход грубой силы состоит в том, чтобы зацикливать каждого из предков предков (включая себя) и увидеть, является ли firstItem или secondItem самим представлением; если это так, удалите это ограничение. Это приведет к правильному решению, возвращая [C0,C1,C2,C3,C4] и только эти ограничения.

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

Ответы

Ответ 1

Единственным решением, которое я нашел до сих пор, является удаление представления из его супервизора:

[view removeFromSuperview]

Похоже, что он удаляет все ограничения, влияющие на его макет, и он готов к добавлению в супервизор и привязан к новым ограничениям. Тем не менее, он будет неправильно удалять любые дочерние элементы из иерархии, а также неправильно избавиться от [C7].

Ответ 2

Этот подход работал для меня:

@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

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


Версия 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

Вы можете удалить все ограничения в представлении, выполнив следующие действия:

[_cell.contentView removeConstraints:_cell.contentView.constraints];

РЕДАКТИРОВАТЬ. Чтобы удалить ограничения для всех подзонов, используйте следующее расширение в Swift:

extension UIView {
    func clearConstraints() {
        for subview in self.subviews {
            subview.clearConstraints()
        }
        self.removeConstraints(self.constraints)
    }
}

Ответ 4

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

Ответ 5

Есть два способа достижения этого в соответствии с документацией Apple для разработчиков

1. NSLayoutConstraint.deactivateConstraints

Это удобный метод, который обеспечивает простой способ деактивации множество ограничений одним вызовом. Эффект этого метода заключается в так же, как установка свойства isActive каждого ограничения в false. Как правило, использование этого метода более эффективно, чем отключение каждого ограничение индивидуально.

// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])

// Usage
NSLayoutConstraint.deactivate(yourView.constraints)

2. UIView.removeConstraints (устарело для> = iOS 8.0)

При разработке для iOS 8.0 или более поздней версии используйте NSLayoutConstraint classs deactivateConstraints: метод вместо вызова removeConstraints: метод напрямую. Деактивировать ограничения: метод автоматически удаляет ограничения из правильных представлений.

// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])'

// Usage
yourView.removeConstraints(yourView.constraints)

Советы

Использование Storyboard или XIB может быть такой болью при настройке ограничений, как указано в вашем сценарии, вы должны создать IBOutlets для каждого, который вы хотите удалить. Несмотря на это, большую часть времени Interface Builder создает больше проблем, чем решает.

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

  1. Создание ваших представлений программно
  2. Разметьте их и используя NSLayoutAnchor
  3. Добавить каждое ограничение, которое может быть удалено позже, в массив
  4. Очистите их каждый раз перед применением нового состояния

Простой код

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

Ссылки

  1. NSLayoutConstraint
  2. UIView

Ответ 6

подробности

  • Xcode 10.2.1 (10E1001), Swift 5

Решение

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

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

print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()

Ответ 7

Я использую следующий метод, чтобы удалить все ограничения из представления:

.h файл:

+ (void)RemoveContraintsFromView:(UIView*)view 
    removeParentConstraints:(bool)parent 
    removeChildConstraints:(bool)child;

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

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

Если вам нужен более детальный контроль, я бы настоятельно рекомендовал перейти на Masonry, мощный каркасный класс, который вы можете использовать всякий раз, когда вам нужно правильно обработать ограничения программным способом.

Ответ 8

Быстрое решение:

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

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

Ответ 9

На основании предыдущих ответов (swift 4)

Вы можете использовать немедленное ограничение, если вы не хотите сканировать целые иерархии.

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

Ответ 10

С целью

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

Ответ 11

Вы можете использовать что-то вроде этого:

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

В основном это перечисляет все ограничения на просмотр viewA и удаляет все ограничения, связанные с viewA.

Затем вторая часть удаляет ограничения из viewA с помощью массива ограничений вида.

Ответ 12

(По состоянию на 31 июля 2017 года)

SWIFT 3

self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)

Цель C

[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];

Это самый простой способ быстро удалить все ограничения, существующие в UIView. Просто не забудьте добавить UIView назад с новыми ограничениями или новым кадром после этого =)

Ответ 13

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

Ответ 14

Это способ отключить все ограничения из определенного представления

 NSLayoutConstraint.deactivate(myView.constraints)