Удалите все ограничения, влияющие на 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
или некоторыми вариантами. Эти решения не будут работать, поскольку эти методы возвращают только ограничения, принадлежащие представлению, а не те, которые влияют на представление.
Чтобы прояснить проблему с этими решениями, рассмотрите эту иерархию представлений:
Теперь представьте, что мы создаем следующие ограничения и всегда присоединяем их к их ближайшему общему предку:
- 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
создает больше проблем, чем решает.
Поэтому, имея очень динамичный контент и разные состояния представления, я бы предложил:
- Создание ваших представлений программно
- Разметьте их и используя NSLayoutAnchor
- Добавить каждое ограничение, которое может быть удалено позже, в массив
- Очистите их каждый раз перед применением нового состояния
Простой код
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()
}
Ссылки
- NSLayoutConstraint
- 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)