Видаліть усі обмеження, що впливають на UIView


87

У мене є 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: Я: така ж, як і Син (належить Мені)
  • C1: Я: ширина = 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 = ТАК; ? ви буквально ніколи не хочете цього для переглядів, які ви встановлюєте з обмеженнями?
lensovet

4
Я видаляю обмеження, тому хочу використовувати обмеження авторозміру після того, як я видалю інші обмеження, щоб утримати його на місці, без цього рядка в моєму коді подання зникне
marchinram

Мені потрібно було реєструвати всі обмеження на подання для налагодження, і я зміг трохи змінити цю відповідь, щоб зробити це. +1
chiliNUT

Це призведе до неправильного видалення C7. Однак це має бути легко виправити, якщо видалити [self removeConstraints:self.constraints];та змінити UIView *superview = self.superview;на UIView *superview = self;.
Почутливий

49

Єдине рішення, яке я знайшов на сьогоднішній день, - це вилучення подання з його супервигляду:

[view removeFromSuperview]

Схоже, він знімає всі обмеження, що впливають на його макет, і готовий до додавання до суперперегляду та додавання нових обмежень. Однак він також буде неправильно видаляти будь-які підпрограми з ієрархії та позбавлятись від них [C7]неправильно.


Ви можете використовувати властивості firstItem і secondItem обмежень, щоб перевірити, чи застосовуються вони до вашого подання, і прокрутити ієрархію подання, щоб знайти їх усі. Я думаю, що видалення та повторне додавання є кращим рішенням. Мені було б цікаво переглянути ваш варіант використання, оскільки, здається, це дуже погано робити. Ваші обмеження можуть легко стати абсолютно недійсними, оскільки інші подання можуть покладатися на ці обмеження, щоб правильно їх викласти.
bjtitus

@bjtitus: Гарна думка. Однак у цьому конкретному випадку я змінюю позицію точки зору, яка від неї нічого не залежить. Він використовує інші погляди, щоб знати, куди його розмістити, і немає переглядів, які б його використовували, щоб знати, де його розмістити.
Почутливий

Не єдине рішення, але дуже просте. У моєму випадку використання я щойно видалив подання із суперперегляду, а потім знову додав його пізніше.
Маріан Черні,

48

Ви можете видалити всі обмеження у поданні, виконавши це:

self.removeConstraints(self.constraints)

РЕДАГУВАТИ: Щоб видалити обмеження всіх підпоглядів, використовуйте таке розширення в Swift:

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

22
Проблема цього рішення полягає в тому, що воно усуває лише обмеження, якими володіє цей вигляд (наприклад, його ширина та висота). Це не знімає таких речей, як провідні та верхні обмеження.
Почутливий

2
Я не перевірив ваш коментар вище. Однак я вважаю, що це буде працювати так само, як і вище, ніж вище, [NSLayoutConstraint disabletivateConstraints: self.constraints];
emdog4

2
Це страждало б від тієї ж проблеми, що і оригінальне рішення. self.constraintsповертає лише обмеження, якими selfволодіє, а не всі обмеження, які впливають self.
Почутливий

1
Я розумію, що ви зараз говорите. У своїй відповіді я видаляю обмеження з contentView UITableViewCell. Потім метод updateConstraints додасть обмеження для всіх підпоглядів і скине макет. У моєму першому коментарі вище, я повинен був ввести self.view.constraints. І self.view, і self.contentView знаходяться у верхній частині ієрархії подання. Встановлення translatorsAutoresizingMasksToConstraints = ТАК на self.view або self.contentView - погана ідея. ;-). Мені не подобаються дзвінки до addSubview: у моєму методі updateConstraints видалення подання з ієрархії здається непотрібним imo.
emdog4,

Я просто оновив запитання, щоб показати, чому таке рішення не буде працювати для того, що я хочу зробити. Якщо припустити, що ви перебуваєте в межах UITableViewCell, еквівалентом contentView є Дід. Тому ваше рішення буде неправильно видалено [C6]та неправильно не видалено [C0,C1,C2].
Почутливий

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 або пізнішої версії використовуйте метод NSLayoutConstraint клас disabletivateConstraints: замість того, щоб безпосередньо викликати метод removeConstraints:. Метод disabletivateConstraints: автоматично видаляє обмеження з правильних подань.

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

Проблема цього рішення полягає в тому, що воно усуває лише обмеження, якими володіє цей вигляд (наприклад, його ширина та висота). Це не знімає таких речей, як провідні та верхні обмеження.
Почутливий

Так, що сказав Senseful. Хоча ці методи дійсно існують, вони самостійно не вирішують проблему, про яку насправді задається питання.
GSnyder

18

Свіфт:

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" :)
Берсаелор

1
Це не зніме всіх обмежень. Обмеження приймають найближчий батько з 2 уражених поглядів, тому, якщо ви зробите обмеження, яке не поділяє нагляд цього перегляду, це обмеження не буде знято ...
vrwim

2
можливо, правильніше було б порівняти ідентичність if c.firstItem === selfзамість кастингу as? UIViewта перевірити рівність із==
user1244109 02

Мабуть, не слід використовувати removeConstraints: from apple doc: // Метод removeConstraints буде застарілим у наступному випуску, і його слід уникати. Натомість використовуйте + [NSLayoutConstraint disabletivateConstraints:].
еоніст

@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
Це досить приємно.
еоніст

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

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


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


2
А як щодо братів і сестер?
zrslv

2

Більш простий та ефективний підхід полягає у видаленні подання з superView та повторному додаванні як підпрогляду. це призводить до того, що всі обмеження підпогляду автоматично видаляються.😉


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

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].
Почутливий

ви можете розробити метод для сканування вгору по ієрархії. Але це рішення є достатньо хорошим, оскільки ви все одно повинні додати обмеження вище безпосереднього нагляду. ІМО
еоніст

0

(Станом на 31 липня 2017 р.)

SWIFT 3

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

Завдання С

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

Це найпростіший спосіб швидко видалити всі обмеження, які існують в UIView. Просто не забудьте додати UIView назад із новими обмеженнями або новим фреймом згодом =)


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

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

Стрімкий

Наступне розширення UIView видалить усі обмеження Edge представлення:

extension UIView {
    func removeAllConstraints() {
        if let _superview = self.superview {
            self.removeFromSuperview()
            _superview.addSubview(self)
        }
    }
}

-1

Це спосіб вимкнути всі обмеження з певного подання

 NSLayoutConstraint.deactivate(myView.constraints)

Проблема цього рішення полягає в тому, що воно усуває лише обмеження, якими володіє цей вигляд (наприклад, його ширина та висота). Це не знімає таких речей, як провідні та верхні обмеження.
Почутливий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.