UIStackView "Невозможно одновременно удовлетворить ограничения" на "скрипучих" скрытых представлениях

Когда мои строки "UIStackView" сжимаются, они вызывают предупреждения AutoLayout. Тем не менее, они отображаются отлично, и ничто другое не является неправильным, кроме этих видов регистрации:

Невозможно одновременно удовлетворить ограничениям.     Вероятно, хотя бы одно из ограничений в следующем списке - это тот, который вы не хотите. Попробуйте это: (1) посмотрите на каждое ограничение и попытайтесь выяснить, чего вы не ожидаете; (2) найти код, который добавил нежелательные ограничения или ограничения и исправить его. (Примечание. Если вы видите NSAutoresizingMaskLayoutConstraints, которые вы не понимаете, обратитесь к документации для свойства UIView, переводитAutoresizingMaskIntoConstraints) (

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

Кто-нибудь знает, как его решить? Интересно, что ограничения компоновки часто отмечаются с помощью 'UISV-hiding', указывая на то, что, возможно, он должен игнорировать минимальные высоты для subviews или что-то в этом случае?

Ответ 1

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

Появилась следующая ошибка:

2015-10-01 11:45:13.732 <redacted>[64455:6368084] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

То, что я пытался сделать, заключалось в размещении UIView внутри моего UIStackView, содержащего вставку UISegmentedControl по 8pts на каждом ребре.

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

Чтобы устранить проблему, я изменил свой 8pt верхний приоритет на нижние ограничения с 1000 до 999, поэтому ограничение UISV-hiding может при необходимости иметь приоритет.

Ответ 2

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

Когда вы вызываете addArrangedSubview(), он автоматически создает ограничения, похожие на следующие:

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

Теперь, когда вы пытаетесь скрыть innerStackView, вы получаете предупреждение о неоднозначных ограничениях.

Чтобы понять, почему, сначала посмотрим, почему это не происходит, когда innerStackView.spacing равно 0. Когда вы вызываете innerStackView.hidden = true, @liamnichols был прав... outerStackView волшебным образом перехватит этот вызов и создаст ограничение 0 height UISV-скрытие с приоритетом 1000 (обязательно). Предположительно, это означает, что элементы в представлении стека будут анимированы вне поля зрения, если ваш скрытый код вызывается в блоке UIView.animationWithDuration(). К сожалению, не существует способа предотвратить добавление этого ограничения. Тем не менее, вы не получите предупреждение "Невозможно одновременно удовлетворить ограничения" (USSC), поскольку происходит следующее:

  • label1 height установлен в 0
  • интервал между двумя метками уже был определен как 0
  • label2 height установлен на 0
  • Высота innerStackView устанавливается равной 0

Ясно, что эти 4 ограничения могут быть выполнены. Просмотр стека просто окутывает все в пиксель высотой 0 дюймов.

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

  • label1 height установлен в 0
  • расстояние между двумя метками было автоматически создано представлением стека как 2 пикселя с приоритетом 1000.
  • label2 height установлен на 0
  • Высота innerStackView устанавливается равной 0

В представлении стека не может быть максимум 0 пикселей, а его содержимое должно быть 2 пикселя. Ограничения не могут быть выполнены.

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

Примечание. По какой-то причине это произошло только в том случае, когда представление в стеке было подчинено UICollectionViewCell или UITableViewCell. Тем не менее, вы все равно можете воспроизвести это поведение вне ячейки, вызвав innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) в следующем цикле запуска после скрытия представления внутреннего стека.

Примечание. Даже если вы попытаетесь выполнить код в UIView.performWithoutAnimations, представление стека все равно добавит ограничение по высоте 0, которое вызовет предупреждение USSC.


Существует не менее 3 решений этой проблемы:

  • Перед тем, как скрыть какой-либо элемент в представлении стека, проверьте, является ли это представлением стека, и если да, измените spacing на 0.. Это раздражает, потому что вам нужно обратить вспять процесс (и помните исходный интервал), когда вы снова показываете содержимое.
  • Вместо того, чтобы скрывать элементы в представлении стека, вызовите removeFromSuperview. Это еще более раздражает, поскольку, когда вы меняете процесс, вам нужно запомнить, куда вставить удаленный элемент. Вы можете оптимизировать, только вызывая removeArrangedSubview, а затем скрываясь, но есть много бухгалтерии, которая еще должна быть выполнена.
  • Обернуть вложенные представления стека (которые имеют ненулевой spacing) в UIView. Задайте хотя бы одно ограничение как необязательный приоритет (999 или ниже). Это лучшее решение, поскольку вам не нужно делать какие-либо бухгалтерские операции. В моем примере я создал верхние, ведущие и конечные ограничения в 1000 между представлением стека и представлением оболочки, а затем создал ограничение 999 от нижней части представления стека к представлению обертки. Таким образом, когда представление внешнего стека создает ограничение нулевой высоты, ограничение 999 нарушается, и вы не видите предупреждение USSC. (Примечание: это похоже на решение Если contentView.translatesAutoResizingMaskToConstraints подкласса UICollectionViewCell устанавливается на false)

Вкратце, причины, по которым вы получаете это поведение:

  • Apple автоматически создает 1000 ограничений приоритета для вас, когда вы добавляете управляемые объекты в представление стека.
  • Apple автоматически создает ограничение 0-height для вас, когда вы скрываете представление в представлении стека.

Если бы Apple (1) разрешала вам указывать приоритет ограничений (особенно проставки) или (2) позволяла вам отказаться от автоматического ограничения UISV-скрытия, эта проблема будет легко разрешено.

Ответ 3

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

Ответ 4

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

Однако, если вам не нужна анимация (возможно, вы скрываете ее в ViewDidLoad), вы можете просто removeFromSuperview, которая будет иметь тот же эффект, но без каких-либо проблем с ограничениями, поскольку они будут удалены вместе с вид.

Ответ 5

Основываясь на @Senseful ответе, вот расширение UIStackView, чтобы обернуть представление стека в представлении и применить ограничения, которые он рекомендует:

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

Вместо добавления stackView используйте stackView.wrapped().

Ответ 6

Сначала, как предложили другие, убедитесь, что ограничения, которые вы можете контролировать, т.е. не ограничения, присущие UIStackView, установлены на приоритет 999, чтобы они могли быть переопределены при скрытии представления.

Если вы все еще испытываете проблему, проблема, скорее всего, связана с интервалом в скрытых StackView. Мое решение состояло в том, чтобы добавить UIView в качестве разделителя и установить интервал UIStackView в ноль. Затем установите ограничения View.height или View.width(в зависимости от вертикального или горизонтального стека) на расстояние StackView.

Затем измените приоритеты сжатия содержимого и сжатия содержимого ваших новых добавленных просмотров. Возможно, вам придется изменить распределение родительского StackView.

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

Ответ 7

Недавно я боролся с ошибками автоматического макета, скрывая UIStackView. Вместо того, чтобы делать кучу хранения книг и обертывания в UIViews, я решил создать выход для своих parentStackView и выходов для детей, которых я хочу скрыть/показать.

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

В раскадровке, как выглядит мой родительский стол:

введите описание изображения здесь

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

В моем примере parentStackViews содержит массив из 4 элементов: Top Stack View, StackViewNumber1, Stack View Number 2 и Stop Button. Их индексы в arrangedSubviews равны 0, 1, 2 и 3 соответственно. Когда я хочу скрыть его, я просто удаляю его из массива parentStackView's arrangedSubviews. Поскольку он не слаб, он задерживается в памяти, и вы можете просто вернуть его обратно по своему желаемому индексу позже. Я не переинициализирую его, поэтому он просто зависает, пока он не понадобится, но не раздувает память.

Итак, в принципе, вы можете...

1) Перетащите IBOutlets для своего родительского стека и детей, которые вы хотите скрыть/показать в раскадровке.

2) Если вы хотите скрыть их, удалите стек, который вы хотите скрыть от массива parentStackView's arrangedSubviews.

3) Вызовите self.view.layoutIfNeeded() с помощью UIView.animateWithDuration.

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

Скажем, я хочу скрыть stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Затем анимируем его:

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Если вы хотите "показать" stackViewNumber2 позже, вы можете просто вставить его в желаемый индекс parentStackView arrangedSubviews и оживить обновление.

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

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

Если у вас есть что-то, что вы хотите скрывать по умолчанию, вы можете просто выложить его на раскадровку и удалить его в viewDidLoad и обновить без анимации с помощью view.layoutIfNeeded().

Ответ 8

Возможно, вы создали ограничение при работе с определенным классом размера (ex: wCompact hRegular), а затем вы создали дубликат при переключении на другой класс размера (например: wAny hAny). проверьте ограничения объектов UI в разных классах размера и посмотрите, есть ли аномалии с ограничениями. вы должны увидеть красные линии, указывающие сталкивающиеся ограничения. Я не могу поместить картинку, пока не получу 10 очков репутации, извините:/

Ответ 9

Мне хотелось скрыть весь UIStackView за один раз, но я получал те же ошибки, что и OP, это исправило это для меня:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}

Ответ 10

У меня был ряд кнопок с ограничениями по высоте. Это происходит, когда одна кнопка скрыта. Установка приоритета ограничения высоты кнопок на 999 решила проблему.

Ответ 11

Я столкнулся с теми же ошибками со встроенными Stack Views, хотя во время работы все отлично работало.

Я решил ошибки ограничения, сначала спрятав все представления подэтапов (установка isHidden = true), прежде чем скрывать представление родительского стека.

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

Надеюсь, это поможет.

Ответ 12

Эта ошибка не имеет ничего общего с UIStackView. Это происходит, когда у вас конфликтующие ограничения с одинаковыми приоритетами. Например, если у вас есть ограничение, указывается, что ширина вашего представления равна 100, и у вас есть еще одно ограничение в то же время, что ширина представления составляет 25% от его контейнера. Очевидно, есть два противоречивых ограничения. Решение состоит в том, чтобы удалить из них.

Ответ 13

NOP с [mySubView removeFromSuperview]. Надеюсь, это может помочь кому-то:)