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


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

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

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

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

13 79

13 ответов:

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

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

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

V:|[innerStackView]|              | = outerStackView

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

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

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

  1. высота label1 установлена в 0
  2. расстояние между двумя метками уже было определено как 0
  3. высота label2 установлена в 0
  4. высота innerStackView установлена в 0

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

теперь вернемся к примеру багги, если мы установим spacing до 2, теперь у нас есть эти ограничения:

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

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

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

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

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


есть по крайней мере 3 решения этой проблемы:

  1. прежде чем скрыться любой элемент в стековом представлении, проверьте, является ли он стековым представлением, и если да, измените spacing к 0. это раздражает, потому что вам нужно отменить процесс (и запомнить исходный интервал) всякий раз, когда вы снова показываете содержимое.
  2. вместо того, чтобы скрывать элементы в стеке, вызовите removeFromSuperview. это еще более раздражает, так как, когда вы отменяете процесс, вам нужно помнить здесь чтобы вставить удаленный элемент. Вы можете оптимизировать только вызов removeArrangedSubview и затем скрытие, но есть много бухгалтерии, которая все еще должна быть сделана.
  3. обернуть вложенные представления стека (которые имеют ненулевые spacing) в UIView. Укажите хотя бы одно ограничение в качестве необязательного приоритета (999 или ниже). это лучшее решение, так как вам не нужно делать какие-либо бухгалтерии. В моем примере я создал ограничения top, leading и trailing на 1000 между представлением стека и представлением оболочки, а затем создал 999 ограничение от нижней части представления стека до представления оболочки. Таким образом, когда внешний вид стека создает ограничение нулевой высоты, ограничение 999 нарушается, и вы не видите предупреждение USSC. (Примечание: это похоже на решение должны на странице просмотр содержимого.translatesAutoResizingMaskToConstraints подкласса UICollectionViewCell устанавливается в false)

в общем, причины, по которым вы получаете такое поведение являются:

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

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

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

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

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

основываясь на ответе @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().

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

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

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

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

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

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

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

enter image description here

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

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

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

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

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

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)

если вы хотите "показать" a stackViewNumber2 позже, вы можете просто вставить его в нужные parentStackViewarrangedSubViews индексировать и анимировать обновление.

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

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

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

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

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

Я испытал те же ошибки со встроенными представлениями стека, хотя все работало нормально во время выполнения.

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

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

надеюсь, что это помогает.

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

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