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

97

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

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

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

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

Бен Гильдия
источник
1
Похоже, что это исправлено в iOS11, и здесь не появляется никаких предупреждений
траппер

Ответы:

206

Вы получаете эту проблему, потому что при установке подпредставления изнутри 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вставку по 8 пунктов на каждом краю.

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

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

liamnichols
источник
Следует отметить, что, похоже, если у вас есть ограничение по высоте или ширине для них и уменьшите их приоритет, это не сработает. Вам нужно удалить высоту / ширину и добавить верхние ведущие конечные основания, затем установить их приоритет как более низкий, и это работает
bolnad
4
Смена приоритетов тоже сработала. Кроме того, удаление лишних (затемненных) ограничений, которые были случайно скопированы из неиспользуемых классов размеров. ВАЖНЫЙ СОВЕТ: чтобы упростить отладку этих проблем, установите строку IDENTIFER для каждого ограничения. Затем вы можете увидеть, какое ограничение было непослушным в отладочном сообщении.
Уомбл
3
В моем случае мне нужно только снизить приоритет по высоте, и это работает.
pixelfreak
Совет IDENTIFIER великолепен! Мне всегда было интересно, как задать ограничения в именах отладочных сообщений, я всегда хотел добавить что-то в представление, а не само ограничение. Спасибо, @Womble!
Райан
Благодарность! Приоритет от 1000 до 999 сделал свое дело. Xcode: Version 8.3.3 (8E3004b)
Майкл Гарито
54

У меня была аналогичная проблема, которую было нелегко решить. В моем случае у меня было представление стека, встроенное в представление стека. Для внутреннего 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 ограничения могут быть удовлетворены. Представление стека просто превращает все в пиксель нулевой высоты.

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

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

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

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

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

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


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

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

Таким образом, причины такого поведения:

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

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

Чувственный
источник
6
Спасибо за очень подробное и полезное объяснение. Однако это определенно кажется ошибкой со стороны Apple при просмотре стека. В основном их функция «сокрытия» несовместима с их функцией «интервала». Есть ли идеи, что они решили эту проблему с тех пор или добавили некоторые функции, чтобы предотвратить взлом с дополнительными содержащими представлениями? (Опять же, большое количество потенциальных решений и действительно согласны с элегантностью # 3)
Marchy
1
Нужно ли обернуть каждого UIStackViewдочернего элемента UIStackViewв a UIViewили только того, который вы хотите скрыть / показать?
Адриан
1
Это похоже на очень плохой упущение со стороны Apple. Особенно потому, что предупреждения и ошибки, связанные с использованием, UIStackViewобычно загадочны и трудны для понимания.
bompf
Это палочка-выручалочка. У меня возникла проблема, когда UIStackViews, добавленные в UITableViewCell, вызывали спам в журнале ошибок AutoLayout каждый раз, когда ячейка использовалась повторно. Встраивание stackView в UIView с низким приоритетом ограничения нижнего якоря решило проблему. Это заставляет отладчик представления отображать элементы stackView как имеющие неоднозначную высоту, но это отображается правильно в приложении, без спама в журнале ошибок. СПАСИБО.
Уомбл
6

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

Лучано Алмейда
источник
Что вы имеете в виду? Все ограничения являются относительными в пределах представлений, которые сложены ....
Бен Гилд
Извините, я не понимаю ваш вопрос правильно, мой английский таков, но я думаю, что ограничения связаны с представлением, с которым оно связано, если представление становится скрытым, ограничения становятся неактивными. Я не уверен, что вы сомневаетесь в этом, но надеюсь, что смогу помочь.
Лучано Алмейда,
Кажется, что это происходит, когда материал сжимается или находится в процессе отображения / скрытия. В этом случае он становится частично видимым. - Может быть, действительно необходимо пройти и исключить любые минимальные вертикальные постоянные, потому что их можно сжать до нулевой высоты?
Ben Guild
2

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

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

Орен
источник
1

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

Бен Паккард
источник
1

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

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

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

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

Питер Койл
источник
1

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

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

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

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

У него 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.

Обратите внимание, что последние два stackView - нет 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().

Адриан
источник
1

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

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

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

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

Уилл Стивенс
источник
1

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

Все, что вам нужно сделать, это установить приоритет всех ограничений stackView ниже 1000 (999 выполнит работу). Например, если stackView ограничен своим супервизором слева, справа, сверху и снизу, тогда все 4 ограничения должны иметь приоритет ниже 1000.

Линь Та
источник
0

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

FM
источник
Ну ладно. но я получил эту ошибку, когда у меня был случай, который я описал drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…
FM
Да, я определенно не вижу красного цвета при переключении классов размеров в конструкторе интерфейсов. Я использовал только размер «Любой».
Ben Guild
0

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

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}
sp00ky
источник
У меня это не сработало, потому что механизм автоматической компоновки жалуется, когда вы меняете required constraint ( priority = 1000) на not required ( priority <= 999).
Senseful
0

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

Никлас
источник
-2

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

Уильям Кинаан
источник
-3

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

Грегуар Гийон
источник
Извините, а что нет?
Панг