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

87

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

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

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

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

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

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

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


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

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

  • Дедушка
    • Отец
      • мне
        • Сын
        • Дочь
      • Родной брат
    • Дядя

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

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

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

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

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

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

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

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

Чувственный
источник
Как насчет того, чтобы присвоить идентификаторы всем ограничениям, которые вы хотите удалить? Таким образом, вам не нужно держать для них розетку.
nsuinteger

Ответы:

69

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

@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
Это должен быть официальный ответ.
Джейсон Крокер
почему у вас self.translatesAutoresizingMaskIntoConstraints = YES; ? вы буквально никогда не хотите этого для представлений, которые вы настраиваете с ограничениями?
линзовет
4
Я удаляю ограничения, поэтому я хочу использовать ограничения автоматического изменения размера после того, как я удалю другие ограничения, чтобы удерживать его на месте, без этой строки в моем коде представление исчезло бы
марчинрам
Мне нужно было зарегистрировать все ограничения в представлении для целей отладки, и я смог немного изменить этот ответ для этого. +1
chiliNUT
Это будет неправильно удалить C7. Однако это должно быть легко исправить, если вы удалите [self removeConstraints:self.constraints];и измените UIView *superview = self.superview;на UIView *superview = self;.
Senseful
49

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

[view removeFromSuperview]

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

Чувственный
источник
Вы можете использовать свойства firstItem и secondItem ограничений, чтобы проверить, применимы ли они к вашему представлению, и пройти через иерархию представлений, чтобы найти их все. Однако я думаю, что удаление и повторное добавление - лучшее решение. Мне было бы интересно увидеть ваш вариант использования, так как кажется, что это очень плохая вещь. Ваши ограничения могут легко стать полностью недействительными, поскольку другие представления могут полагаться на эти ограничения для правильной компоновки.
bjtitus
@bjtitus: Хорошее замечание. Однако в этом конкретном случае я изменяю позицию, от которой ничего не зависит. Он использует другие представления, чтобы знать, где их разместить, и нет представлений, которые используют его, чтобы знать, где их разместить.
Senseful
Не единственное решение, но очень простое. В моем случае использования я только что удалил представление из супервизора, а затем повторно добавил его позже.
Marián Černý
48

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

self.removeConstraints(self.constraints)

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

extension UIView {
    func clearConstraints() {
        for subview in self.subviews {
            subview.clearConstraints()
        }
        self.removeConstraints(self.constraints)
    }
}
emdog4
источник
22
Проблема с этим решением заключается в том, что оно удаляет только ограничения, которыми владеет это представление (например, его ширина и высота). Он не удаляет такие вещи, как ведущие и верхние ограничения.
Senseful 06
2
Я не проверял ваш комментарий выше. Однако я считаю, что это будет работать так же или лучше, чем указано выше, [NSLayoutConstraint deactivateConstraints: self.constraints];
emdog4 08
2
Это будет иметь ту же проблему, что и исходное решение. self.constraintsвозвращает только ограничения, которыми selfвладеет, а не все ограничения, которые влияют self.
Senseful 08
1
Я понимаю, о чем вы сейчас говорите. В своем ответе я снимаю ограничения с contentView UITableViewCell. Затем метод updateConstraints добавит ограничения для всех подпредставлений и сбросит макет. В моем первом комментарии выше я должен был ввести self.view.constraints. И self.view, и self.contentView находятся наверху иерархии представлений. Установка translatesAutoresizingMasksToConstraints = YES для self.view или self.contentView - плохая идея. ;-). Мне не нравятся вызовы addSubview: в моем методе updateConstraints удаление представления из иерархии кажется ненужным.
emdog4
Я только что обновил вопрос, чтобы показать, почему подобное решение не подходит для того, что я собираюсь делать. Предполагая, что вы находитесь в UITableViewCell, эквивалентом contentView является Grandfather. Поэтому ваше решение неправильно удалит [C6], а неправильно не удалит [C0,C1,C2].
Senseful
20

Согласно документации разработчика 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 или более поздних версий используйте метод deactivateConstraints: класса NSLayoutConstraint вместо прямого вызова метода removeConstraints :. Метод deactivateConstraints: автоматически удаляет ограничения из правильных представлений.

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

// Usage
yourView.removeConstraints(yourView.constraints)

подсказки

Использование Storyboards или XIBs может быть такой проблемой при настройке ограничений, как указано в вашем сценарии, вам нужно создать 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
Э-Ридди
источник
2
Быть осторожен. Представления, которые определяют внутренний размер содержимого, будут иметь NSContentSizeLayoutConstraint, автоматически добавляемый iOS. Удаление этого путем доступа к обычному свойству yourView.constraints нарушит автоматическое размещение этих представлений.
TigerCoding 06
Проблема с этим решением заключается в том, что оно удаляет только ограничения, которыми владеет это представление (например, его ширина и высота). Он не удаляет такие вещи, как ведущие и верхние ограничения.
Senseful
Да, что сказал Сенсфул. Хотя эти методы действительно существуют, они сами по себе не решают проблему, о которой на самом деле задается вопрос.
GSnyder
18

В 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)
    }
}
Александр Волков
источник
Это приведет к сбою, если ваше представление не имеет супервизора в супервизоре! Пожалуйста, заключите эту часть в 'if let superview = superview' :)
Bersaelor
1
Это не снимет всех ограничений. Ограничения берут ближайшего родителя из 2 затронутых представлений, поэтому, если вы
создадите
2
возможно, более правильным было бы сравнить идентичность if c.firstItem === selfвместо приведения as? UIViewи проверить равенство с помощью==
user1244109 02
Вероятно, не следует использовать removeConstraints: from apple doc: // Метод removeConstraints будет устаревшим в будущем выпуске, и его следует избегать. Вместо этого используйте + [NSLayoutConstraint deactivateConstraints:].
eonist
@eonist ответ - 3 года назад. Вместо критики ее следует отредактировать.
Александр Волков
5

Детали

  • 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()
Василий Боднарчук
источник
1
Это очень мило.
eonist
2

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

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

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

Guig
источник
2

На основе предыдущих ответов (быстрый 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
}
}
эонист
источник
2

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

.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 , мощный класс фреймворка, который вы можете использовать всякий раз, когда вам нужно правильно обрабатывать ограничения программно.

Darkseal
источник
2
А как насчет братьев и сестер?
zrslv
2

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

Хашем Абунаджми
источник
1

С объективомC

[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];
}
Giang
источник
0

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

[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, используя массив ограничений viewA.

Тристан
источник
1
Проблема с этим решением состоит в том, что оно удаляет только ограничения, принадлежащие супервизору. Он не снимает всех ограничений, влияющих на представление. (Например, вы могли бы так же легко установить ограничения для
супервизора супервизора супервизора
Я просто обновил вопрос, чтобы показать, почему это решение не работает. Это наиболее близко к правильному, поскольку оно наиболее близко к решению с использованием грубой силы, о котором я упоминал выше. Однако это решение будет удалять [C7]неправильно и не удаляет неправильно [C3,C4].
Senseful
вы можете разработать метод ползания вверх по иерархии. Но это решение достаточно хорошее, так как вы все равно должны добавить ограничения выше непосредственного супервизора. IMO
eonist
0

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

SWIFT 3

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

Цель C

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

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

BennyTheNerd
источник
0

Использование многоразовой последовательности

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

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

public extension Array where Element == NSLayoutConstraint {

    func affectingView(_ targetView:UIView) -> [NSLayoutConstraint] {

        return self.filter{

            if let firstView = $0.firstItem as? UIView,
                firstView == targetView {
                return true
            }

            if let secondView = $0.secondItem as? UIView,
                secondView == targetView {
                return true
            }

            return false
        }
    }
}

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

public struct AllConstraintsSequence : Sequence {

    public init(view:UIView){
        self.view = view
    }

    public let view:UIView

    public func makeIterator() -> Iterator {
        return Iterator(view:view)
    }

    public struct Iterator : IteratorProtocol {

        public typealias Element = (constraint:NSLayoutConstraint, owningView:UIView)

        init(view:UIView){
            targetView  = view
            currentView = view
            currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
        }

        private let targetView  : UIView
        private var currentView : UIView
        private var currentViewConstraintsAffectingTargetView:[NSLayoutConstraint] = []
        private var nextConstraintIndex = 0

        mutating public func next() -> Element? {

            while(true){

                if nextConstraintIndex < currentViewConstraintsAffectingTargetView.count {
                    defer{nextConstraintIndex += 1}
                    return (currentViewConstraintsAffectingTargetView[nextConstraintIndex], currentView)
                }

                nextConstraintIndex = 0

                guard let superview = currentView.superview else { return nil }

                self.currentView = superview
                self.currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
            }
        }
    }
}

Наконец, мы объявляем расширение UIViewдля отображения всех влияющих на него ограничений в простом свойстве, к которому вы можете получить доступ с помощью простого синтаксиса for-each.

extension UIView {

    var constraintsAffectingView:AllConstraintsSequence {
        return AllConstraintsSequence(view:self)
    }
}

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

Перечислите их идентификаторы ...

for (constraint, _) in someView.constraintsAffectingView{
    print(constraint.identifier ?? "No identifier")
}

Деактивировать их ...

for (constraint, _) in someView.constraintsAffectingView{
    constraint.isActive = false
}

Или удалите их полностью ...

for (constraint, owningView) in someView.constraintsAffectingView{
    owningView.removeConstraints([constraint])
}

Наслаждайтесь!

Марк А. Донохью
источник
0

Swift

Следующее расширение UIView удалит все ограничения Edge представления:

extension UIView {
    func removeAllConstraints() {
        if let _superview = self.superview {
            self.removeFromSuperview()
            _superview.addSubview(self)
        }
    }
}
Гурджит Сингх
источник
-1

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

 NSLayoutConstraint.deactivate(myView.constraints)
user2501116
источник
Проблема с этим решением заключается в том, что оно удаляет только ограничения, которыми владеет это представление (например, его ширина и высота). Он не удаляет такие вещи, как ведущие и верхние ограничения.
Senseful