Що таке 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 dev: devforums.apple.com/thread/238803
Rog

3
Я вирішив подібну проблему наступним чином, і вона працює на iOS 7/8. 1) Опустіть один із пріоритетів обмеження до 750. Я б спробував 1-й чи 2-й 2) У підкласі комірок у наборі пробуджених від NOB self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;. Я думаю, що спочатку встановлення маски автоматичного розміру зупиняє додавання останнього обмеження. Я знайшов це рішення тут: github.com/wordpress-mobile/WordPress-iOS/commit/…
Джессі

3
@RogerNolan, будь-які новини? Я виявив ту саму проблему під час гри з програмою Auto-layout у Interface Builder. Деякі клітини викликають цю проблему, а деякі ні.
orkenstein

3
Я не думаю, що додавання позначки для автоматичного визначення - це гарне рішення.
Рог

1
@RogerNolan Ви генеруєте цю клітинку в IB перед виконанням цих змін компонування? Я налагоджував ту саму проблему, але не додавав зайвих обмежень. Мені вдалося придушити попередження, відновивши свій погляд з нуля, і коли я розіграв два файли розкадрів, єдиною різницею було те, що у версії з попередженнями відсутня лінія <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 - вона належить до попереднього макета.
Rog

7
UIView-Encapsulated-Layout-HeightОбмеження додає UITableView після того , як висота визначається. Я обчислюю висоту, виходячи systemLayoutSizeFittingSizeз contentView. Тут UIView-Encapsulated-Layout-Heightневажливо. Потім tableView встановлює contentSize явно значенням, поверненим heightForRowAtIndexPath:. У цьому випадку правильно знизити пріоритет наших спеціальних обмежень, оскільки обмеження tableView повинно мати перевагу після обчислення рядків висоти.
Ортвін Генц

8
@OrtwinGentz: Все ж не зрозумійте, що правильно знижувати пріоритет наших спеціальних обмежень, оскільки обмеження tableView повинно мати перевагу після обчислення рядка висоти . Вся справа в тому, що UIView-Encapsulated-Layout-Heightце неправильно, якщо я не знижую пріоритет ...
тестування

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

9
-1 для цієї відповіді, оскільки передбачає, що додане обмеження є правильним. Додане UIView-Encapsulated-Layout-Widthв моєму випадку просто неправильне, але, здається, воно надає перевагу над моїми явними обмеженнями під час виконання.
промінь

68

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

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

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

Це не просто попередження. Макет клітинки мого перегляду таблиці жахливий - весь текст перекривається в одному рядку тексту.

Мене дивує, що наступний рядок "виправляє" мою проблему магічно (авторозкладка нічого не скаржиться, і я отримую те, що очікую на екрані):

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
Хе. Я знову опинився на цій самій відповіді і пішов реалізовувати її з радістю та надією. Знову ж таки, для мене це не мало значення :-)
Бенджон

3
Оцінки, що повертаються tableView: оцінкуHeightForRowAtIndexPath: ОБОВ'ЯЗКОВО має бути принаймні великим, як комірка. В іншому випадку обчислена висота таблиці буде меншою від фактичної висоти, і таблиця може прокручуватися вгору (наприклад, після того, як розмотаний шев повернеться до таблиці). UITableViewAutomaticDimension не слід використовувати.
Мет

1
Зауважте, що чим нижче, тим estimatedRowHeightчастіше cellForRowAtIndexPathбудуть називатися спочатку. висота огляду таблиці, поділена на оцінну кількість разів, як точність. У 12-дюймовому iPad Pro це потенційно може бути числом у тисячах і забиватиме джерело даних, а це може призвести до значної затримки.
Mojo66

32

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

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

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

до:

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

Насправді я можу додати пріоритет до будь-якого з елементів цього обмеження, і він спрацює. Здається, неважливо, який саме. Мої комірки закінчуються належної висоти, і попередження не відображається. Roger, наприклад, спробуйте додати @500відразу після 388обмеження значення висоти (наприклад 388@500).

Я не зовсім впевнений, чому це працює, але я трохи проаналізував. У перерахунку NSLayoutPriority виявляється, що рівень NSLayoutPriorityFittingSizeCompressionпріоритету є 50. Документація для цього рівня пріоритету говорить:

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

Документація для посилального fittingSizeповідомлення говорить:

Мінімальний розмір представлення, який задовольняє обмеженням, які він має. (лише для читання)

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

Я не перекопував це, але, здається, має сенс, що це має щось спільне з тим, де проблема лежить.


Токс Джефф. Все ще відчуває себе клопом до мене. Apple не відповіла на rdar, хоча :-(
Rog

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
Запропонуйте нагороду все-таки принаймні подати відповідь. Здається, що ТАК дозволить просто випаровуватися інакше.
Рог

6

Інша можливість:

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

- (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
Лев,

Похитливо; вилучення роздільника - це те, що мій стіл поводився, тому дякую.
royalmurder

5

Як згадував у коментарі Джессі , це працює для мене:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

FYI, ця проблема не виникає в iOS 10.


2
У Swift 4.2: self.contentView.autoresizingMask = [.ffleHeight]
airowe

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!

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;

Де ти це зробив? В layoutSubviews?
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) верх (10 @ 1000) конфліктує з (UIView-Encapsulated-Layout-Height: 0 @ 1000),

через них пріоритети рівні 1000. Нам потрібно встановити пріоритет під UIView-Encapsulated-Layout-Heightпріоритетом.


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


0

Встановити це view.translatesAutoresizingMaskIntoConstraints = NO;має вирішити цю проблему.


Це прекрасно працювало для мене, навіть якщо це не було ідеальним рішенням.
Енха

0

У мене була схожа проблема з осередком перегляду колекції.

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

Висота комірки була правильною, і попередження відходили.

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