Что такое NSLayoutConstraint «UIView-Encapsulated-Layout-Height» и как я могу заставить его пересчитать чисто?

262

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

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

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

-(void)collapse:(BOOL)collapse; {

    _collapsed = collapse;

    if(collapse)
        [_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0
    else
        [_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]];

    [self setNeedsUpdateConstraints];

}

Всякий раз, когда я делаю это, я оборачиваю это в tableViewобновления и вызываю [tableView setNeedsUpdateConstraints]:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView setNeedsUpdateConstraints];
// I have also tried 
// [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
// with exactly the same results.

[tableView endUpdates];

Когда я делаю это, моя ячейка расширяется (и оживляет во время этого), но я получаю предупреждение об ограничениях:

2014-07-31 13:29:51.792 OneFlatEarth[5505:730175] 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:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>",

    "<NSLayoutConstraint:0x7f94dced2260 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']-(15)-|   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced2350 V:|-(6)-[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced6480 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f94de5773a0(91)]>"
 )

Will attempt to recover by breaking constraint 

<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>

388 - это моя расчетная высота, остальные ограничения для UITextViewменя - из Xcode / IB.

Последнее беспокоит меня - я предполагаю, что UIView-Encapsulated-Layout-Heightэто вычисленная высота ячейки, когда она впервые отображается - (я установил мою UITextViewвысоту равной> = 70.0), однако не похоже, что это производное ограничение отменяет обновлен пользователь cnstraint.

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

Итак, что это NSLayoutConstraint UIView-Encapsulated-Layout-Height(я предполагаю, что это расчетная высота для автоматического определения размера ячеек) и как мне заставить его пересчитывать чисто?

Рог
источник
3
кросс опубликован на форумах разработчиков Apple: devforums.apple.com/thread/238803
Rog
3
Я решил похожую проблему следующим образом, и она работает на iOS 7/8. 1) Понизьте один из приоритетов ограничения до 750. Я бы попробовал 1-й или 2-й 2) В подклассе ячейки в наборе awakeFromNib self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;. Я думаю, что изначально установка маски авторазмера останавливает добавление последнего ограничения. Я нашел это решение здесь: github.com/wordpress-mobile/WordPress-iOS/commit/…
Джесси
3
@RogerNolan, есть новости? Я обнаружил ту же проблему во время игры с автоматическим макетом в Интерфейсном Разработчике. Некоторые клетки вызывают эту проблему, некоторые нет.
Оркенштейн
3
Я не думаю, что добавление метки авторазмера является хорошим решением.
Рог
1
@RogerNolan Вы генерируете эту ячейку в IB до выполнения этих изменений макета? Я отлаживал ту же проблему, но я не добавлял никаких дополнительных ограничений. Мне удалось подавить предупреждение, перестроив свое представление с нуля, и когда я разобрал 2 файла раскадровки, единственное отличие заключалось в том, что в версии с предупреждениями отсутствовала строка <rect key="frame" x="0.0" y="0.0" width="600" height="110"/>в ее определении, что заставило меня поверить, что это ошибка IB. , По крайней мере, мой был в любом случае.
Элл Нил

Ответы:

301

Попробуйте снизить приоритет вашего _collapsedtextHeightConstraintдо 999. Таким образом, UIView-Encapsulated-Layout-Heightограничение системы всегда имеет приоритет.

Это основано на том, что вы вернетесь -tableView:heightForRowAtIndexPath:. Убедитесь, что вы вернули правильное значение, и ваши собственные ограничения и сгенерированные должны быть одинаковыми. Более низкий приоритет для вашего собственного ограничения необходим только временно, чтобы предотвратить конфликты, пока анимация свертывания / развертывания находится в полете.

Ортвин Генц
источник
74
Это достигло бы полной противоположности того, что я хочу. UIView-Encapsulated-Layout-Height неверен - он принадлежит предыдущему макету.
Рог
7
UIView-Encapsulated-Layout-HeightОграничение добавляет UITableView после того , как высота определяется. Я рассчитываю высоту на основе systemLayoutSizeFittingSizeконтента. Здесь UIView-Encapsulated-Layout-Heightне имеет значения. Затем tableView явно устанавливает contentSize в значение, возвращаемое heightForRowAtIndexPath:. В этом случае правильно уменьшить приоритет наших пользовательских ограничений, потому что ограничение tableView должно иметь приоритет после вычисления rowHeights.
Ортвин Генц
8
@OrtwinGentz: Все еще не понимаю, что правильно снижать приоритет наших пользовательских ограничений, потому что ограничение tableView должно иметь приоритет после вычисления rowHeights . Дело в том, что UIView-Encapsulated-Layout-Heightэто неправильно, если я не понизил приоритет ...
тестирование
38
Я утверждаю, что это позволяет избежать конфликта, не решая реальную проблему - хотя это все еще похоже на ошибку Apple, я думаю, что это, вероятно, правильно. Особенно в свете того факта, что Apple пересчитывает это ограничение и все правильно выкладывается после того, как ошибка печатается.
Rog
9
-1 для этого ответа, поскольку предполагается, что добавляемое ограничение является правильным. Добавленное UIView-Encapsulated-Layout-Widthв моем случае просто неправильно, но кажется, что оно предпочтительнее моих явных ограничений во время выполнения.
луч
68

У меня похожий сценарий: табличное представление с одной ячейкой строки, в которой есть несколько строк объектов UILabel. Я использую iOS 8 и autolayout.

Когда я повернулся, я получил неправильную систему, рассчитанную высоту строки (43,5 намного меньше, чем фактическая высота). Это выглядит как:

"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"

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

Меня удивляет, что следующая строка «волшебным образом» решает мою проблему (autolayout ничего не жалует, и я получаю то, что ожидаю на экране):

myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works

с или без этой строки:

myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem
Золотой палец
источник
8
В заключение! Это правильный ответ. В сеансе WWDC было упомянуто, что если вы собираетесь использовать автоматическое определение размера строки, то вы должны установить оценку RowHeight или произойдут плохие вещи. (Да, у Apple действительно произошли неприятные вещи)
Абдалрахман Шату
50
FWIW, добавление оценки не имеет никакого значения для меня.
Бенджон
3
Хех. Я снова вернулся к тому же самому ответу и приступил к его реализации с радостью и надеждой. Еще раз, это не имеет никакого значения для меня :-)
Benjohn
3
Оценки, возвращаемые tableView :timateHeightForRowAtIndexPath: ДОЛЖНЫ быть не меньше, чем ячейка. В противном случае вычисленная высота таблицы будет меньше, чем фактическая высота, и таблица может прокрутиться вверх (например, после того, как раскрутка перейдет к таблице). UITableViewAutomaticDimension не должен использоваться.
Мэтт
1
Обратите внимание, что чем ниже, тем estimatedRowHeightчаще cellForRowAtIndexPathбудет вызываться изначально. высота представления таблицы, поделенная на расчетное значение RowHeight, если быть точным. На 12-дюймовом iPad Pro это может быть число в тысячах, что приведет к перебоям в источнике данных и может привести к значительной задержке.
Mojo66 30.12.16
32

Мне удалось убрать предупреждение, указав приоритет одного из значений в ограничении, которое в предупреждающих сообщениях указано, что оно должно было выйти из строя (ниже "Will attempt to recover by breaking constraint"). Похоже, что пока я устанавливаю приоритет на что-то большее 49, предупреждение исчезает.

Для меня это означало изменение моего ограничения, в предупреждении говорилось, что оно попыталось сломаться:

@"V:|[contentLabel]-[quoteeLabel]|"

чтобы:

@"V:|-0@500-[contentLabel]-[quoteeLabel]|"

Фактически, я могу добавить приоритет любому элементу этого ограничения, и оно будет работать. Кажется, не имеет значения, какой именно. Мои клетки в конечном итоге нужной высоты, и предупреждение не отображается. Роджер, для вашего примера, попробуйте добавить @500сразу после 388ограничения значения высоты (например 388@500).

Я не совсем уверен, почему это работает, но я провел небольшое расследование. В перечислении NSLayoutPriority кажется, что NSLayoutPriorityFittingSizeCompressionуровень приоритета равен 50. Документация для этого уровня приоритета гласит:

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

Документация для ссылочного fittingSizeсообщения гласит:

Минимальный размер представления, который удовлетворяет ограничениям, которые он держит. (Только для чтения)

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

Я не копался в этом, но, похоже, имеет смысл, что это как-то связано с проблемой.

Джефф Боуэн
источник
Тэткс Джефф. Все еще чувствует себя как ошибка для меня. Apple, не ответил на rdar, хотя :-(
Рог
13

В 99,9% случаев при использовании пользовательских ячеек или заголовков все конфликты UITableViewsвозникают при первой загрузке таблицы. После загрузки вы больше не увидите конфликт.

Это происходит потому, что большинство разработчиков обычно используют фиксированную высоту или привязку какого-либо рода для размещения элемента в ячейке / заголовке. Конфликт возникает потому, что при UITableViewпервой загрузке / выкладке он устанавливает высоту своих ячеек равной 0. Это, очевидно, противоречит вашим собственным ограничениям. Чтобы решить эту проблему, просто установите любые фиксированные ограничения высоты на более низкий приоритет ( .defaultHigh). Внимательно прочитайте консольное сообщение и посмотрите, какое ограничение система макета решила нарушить. Обычно это тот, который нуждается в изменении приоритета. Вы можете изменить приоритет следующим образом:

let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
    companyNameTopConstraint.priority = .defaultHigh

NSLayoutConstraint.activate([
            companyNameTopConstraint,
           the rest of your constraints here
            ])
ТЫ С УМА СОШЕЛ
источник
1
Прекрасное объяснение.
Гленн
12

Я был в состоянии устранить эту ошибку, удалив поддельный , cell.layoutIfNeeded()что у меня в tableView«s cellForRowAtметоде.

Клифтон Лабрум
источник
1
Да, это решило проблему и для меня. Я делал ограничения на компоновку кода, поэтому сначала подумал, что могу что-то упустить. Спасибо
Джон
1
Тоже самое! Спасибо!
Андрей Чернуха
7

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

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView reloadRowsAtIndexPaths:@[[tableView indexPathForCell:_briefCell]] withRowAnimation:UITableViewRowAnimationNone];

[tableView endUpdates];

UIView-Encapsulated-Layout-Height Вероятно, это высота таблицы, рассчитанная для ячейки во время начальной загрузки на основе ограничений ячейки в то время.

Остин
источник
Я должен был сказать в своем вопросе, я попробовал это, и это не работает. Я согласен с вашим предположением о UIView-Encapsulated-Layout-Height
Rog
6
В любом случае имейте награду за представление ответа. Похоже, что SO просто позволит ему испариться иначе.
Рог
6

Другая возможность:

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

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
   [sizingCell setNeedsLayout];
   [sizingCell layoutIfNeeded];
   CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
   return size.height; // should + 1 here if my uitableviewseparatorstyle is not none

}
Каллен СОЛНЦЕ
источник
Это помогло мне в случае, когда я получил неоднозначности высоты макета в довольно сложной компоновке ячеек только в некоторых симуляторах (iPad 6 Plus). Мне кажется, что из-за некоторых внутренних ошибок округления содержимое немного сжато, и если ограничения не готовы к сжатию, я получил двусмысленности. Таким образом , вместо того , чтобы вернуться UITableViewAutomaticDimensionв heightForRowAtIndexPathI вернуться [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
Лео
Я имею в виду, конечно[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
Лев
ошеломляюще; удаление разделителя - вот что заставило мою таблицу вести себя, так что спасибо.
королевское убийство
5

Как упомянул Джесси в комментарии вопроса, это работает для меня:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

К вашему сведению, эта проблема не возникает в iOS 10.

Фу Нгуен
источник
2
В Swift 4.2: self.contentView.autoresizingMask = [.f flexHeight]
19
4

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

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

let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!
Che
источник
2
Это был комментарий, который дал мне понять мою проблему. У меня есть ячейка изображения, которая загружается динамически (растёт и сжимается) и табличное представление с оценкой RowHeight = 50 и rowHeight = UITableViewAutomaticDimension. Я все еще нарушал ограничения, хотя табличные представления были правильной высоты. Оказалось, что разделители имели высоту 0,3333, и это было то, что пинало мое ограничение размера изображения в ячейке, которую нужно разрушить. После выключения сепараторов все было хорошо. Спасибо Че за то, что дал мне то, что искать.
migs647
1
В этом случае создайте дополнительное ограничение, например, bottomMargin> = view.bottomMargin+1@900. AutoLayout пытается разместить лишнюю 1 точку, изменяет размеры ячейки, запутывается из-за высоты разделителя, пытается преодолеть / ослаблять некоторые ограничения, находит единицу @ 900 и отбрасывает ее. Вы получите нужный макет без предупреждений.
Антон
спаси мой день несколько часов в любом случае
Антон Тропашко
1

Изменение размера текстового представления в соответствии с его содержимым и обновление константы ограничения высоты в соответствии с результирующей высотой позволило UIView-Encapsulated-Layout-Heightустранить конфликт ограничений, например:

[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;
Ким Андре Санд
источник
Где ты это сделал? В макете Subviews?
stuckj
1

Потратив несколько часов, почесывая голову с этой ошибкой, я наконец нашел решение, которое сработало для меня. Моя главная проблема заключалась в том, что у меня было зарегистрировано несколько перьев для разных типов ячеек, но одному типу ячеек было разрешено иметь разные размеры (не все экземпляры этой ячейки будут одинакового размера). поэтому проблема возникла, когда табличное представление пыталось удалить из очереди ячейку этого типа, и оказалось, что она имеет другую высоту. Я решил это, установив

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; self.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

что-то вроде

cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; cell.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

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

Алсидес Эдуардо Селайя
источник
0

TableView получает высоту для ячейки в indexPath от делегата. затем получить ячейку из cellForRowAtIndexPath:

top (10@1000)
    cell
bottom (0@1000)

если cell.contentView.height: 0 // <-> (UIView-Encapsulated-Layout-Height: 0 @ 1000) top (10 @ 1000) конфликтует с (UIView-Encapsulated-Layout-Height: 0 @ 1000),

потому что их приоритеты равны 1000. Нам нужно установить верхний приоритет в соответствии UIView-Encapsulated-Layout-Heightс приоритетом.

user2962814
источник
0

Я получаю сообщение, подобное этому:

Невозможно одновременно удовлетворить ограничения ...
...
...
...
NSLayoutConstraint: 0x7fe74bdf7e50 'UIView-Encapsulated-Layout-Height' V: [UITableViewCellContentView: 0x7fe75330c5c0 (21.5)]
...
...
Попытка восстановления выполняется с помощью нарушение ограничения NSLayoutConstraint: 0x7fe0f9b200c0 UITableViewCellContentView: 0x7fe0f9b1e090.bottomMargin == UILabel: 0x7fe0f9b1e970.bottom

Я использую кастом UITableViewCellс UITableViewAutomaticDimensionвысотой. И я также реализовал estimatedHeightForRowAtIndex:метод.

Ограничение, которое доставляло мне проблемы, выглядело примерно так

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[title]-|" options:0 metrics:nil views:views];

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

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6@999-[title]-6@999-|" options:0 metrics:nil views:views];

Однако, что я заметил, так это то, что если я на самом деле просто удаляю приоритет, это тоже работает, и я не получаю журналы нарушения ограничения:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6-[title]-6-|" options:0 metrics:nil views:views];

Это немного загадка в том, что разница между |-6-[title]-6-|и |-[title-|. Но указание размера не является проблемой для меня и избавляет от логов, и мне не нужно снижать приоритет моих необходимых ограничений.

user1687195
источник
0

Установите это view.translatesAutoresizingMaskIntoConstraints = NO;должно решить эту проблему.

Кэти Чжоу
источник
Это сработало идеально для меня, даже если это не было идеальным решением.
Enkha
0

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

Я решил это, понизив приоритет конечного ограничения, связанного с нижней частью ячейки (последней в цепочке сверху вниз в представлении - это, в конечном счете, определяет его высоту) до 999.

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

Богатый
источник