TL; DR: Не любиш читати? Перейдіть прямо до зразкових проектів на GitHub:
Концептуальний опис
Перші два кроки нижче застосовуються незалежно від того, для якої версії iOS ви розробляєте.
1. Налаштування та додавання обмежень
У своєму UITableViewCell
підкласі додайте обмеження, щоб їхні ребра підкліпа були прикріплені до країв contentView комірки (головне - до верхнього І нижнього краю). ПРИМІТКА: не закріплюйте підвиди до самої комірки; тільки до осередку contentView
! Нехай внутрішній розмір вмісту в цих підглядах визначає висоту перегляду вмісту комірки подання таблиці, переконуючись, що опір стиснення вмісту та обмеження, пов’язані з обіймом вмісту у вертикальному вимірі для кожного підперегляду, не перекриваються доданими обмеженнями більш високого пріоритету. (Так ? Натисніть тут. )
Пам'ятайте, ідея полягає в тому, щоб підгляди клітинки були підключені вертикально до перегляду вмісту комірки, щоб вони могли "чинити тиск" і змушувати розширення перегляду вмісту відповідати їм. Використовуючи комірку з прикладом з кількома підзаглядами, ось наочна ілюстрація того, як повинні виглядати деякі (не всі!) Ваших обмежень:
Ви можете собі уявити, що як більше тексту додається до багаторядкової мітки тіла у комірці зразка вище, для збільшення розміру тексту потрібно буде рости вертикально, що ефективно змусить клітинку зростати у висоту. (Звичайно, вам потрібно виправити обмеження для того, щоб це працювало правильно!)
Правильне встановлення ваших обмежень, безумовно, є найважчою та найважливішою частиною отримання динамічної висоти комірки, що працює з автоматичним макетом. Якщо ви тут помилитесь, це може завадити роботі всіх інших - так що витрачайте час! Я рекомендую встановити свої обмеження в коді, оскільки ви точно знаєте, які обмеження додаються куди, і набагато простіше налагоджувати, коли справи йдуть не так. Додавання обмежень у код може бути настільки ж простим і значно потужнішим, ніж Interface Builder за допомогою якорів компонування або одного з фантастичних API з відкритим кодом, доступних на GitHub.
- Якщо ви додаєте обмеження в код, ви повинні зробити це один раз у межах
updateConstraints
методу свого підкласу UITableViewCell. Зауважте, що вони updateConstraints
можуть викликатися не один раз, тому, щоб уникнути додавання одних і тих же обмежень неодноразово, обов’язково загортайте код додавання обмежень updateConstraints
у перевірку на булеву властивість, таку якdidSetupConstraints
(яку ви встановите на ТАК після запуску обмеження). -додавання коду один раз). З іншого боку, якщо у вас є код, який оновлює існуючі обмеження (наприклад, коригування constant
властивості на деякі обмеження), вставте це в updateConstraints
чек, але поза його межами, didSetupConstraints
щоб він міг працювати щоразу, коли метод викликається.
2. Визначте унікальні ідентифікатори повторного використання таблиці
Для кожного унікального набору обмежень у комірці використовуйте унікальний ідентифікатор повторного використання комірки. Іншими словами, якщо ваші клітини мають більше одного унікального макета, кожен унікальний макет повинен отримати власний ідентифікатор повторного використання. (Хороший натяк на те, що вам потрібно використовувати новий ідентифікатор повторного використання, - це коли у вашому варіанті комірки є інша кількість підпрезентацій або підпогляди розташовані чітко.)
Наприклад, якщо ви відображали повідомлення електронної пошти у кожній комірці, у вас можуть бути 4 унікальних макета: повідомлення з лише темою, повідомлення з темою та тілом, повідомлення з темою та вкладенням фотографій та повідомлення з темою, корпус та вкладення для фотографій. Кожен макет має абсолютно різні обмеження, необхідні для його досягнення, тому після ініціалізації комірки та додавання обмежень для одного з цих типів комірок комірка повинна отримати унікальний ідентифікатор повторного використання, характерний для цього типу клітин. Це означає, що коли ви видалите комірок для повторного використання, обмеження вже додані і готові перейти до цього типу комірок.
Зауважте, що через відмінності у внутрішньому розмірі вмісту комірки з однаковими обмеженнями (типом) все ще можуть мати різну висоту! Не плутайте принципово різні макети (різні обмеження) з різними обчисленими кадрами перегляду (вирішеними з однакових обмежень) через різні розміри вмісту.
- Не додайте комірки з абсолютно різними наборами обмежень до одного пулу повторного використання (тобто використовуйте той самий ідентифікатор повторного використання), а потім намагайтеся видалити старі обмеження та встановити нові обмеження з нуля після кожного декею. Внутрішній механізм автоматичного макета не розроблений для вирішення масштабних змін обмежень, і ви побачите масові проблеми з продуктивністю.
Для iOS 8 - Самостійні розміри комірок
3. Увімкніть оцінку висоти рядків
Щоб увімкнути саморозмір клітинок подання таблиці, потрібно встановити для властивості rowHeight таблиці перегляду UITableViewAutomaticDimension. Ви також повинні призначити значення властивості оцінюваногоRowHeight. Як тільки обидва ці властивості встановлені, система використовує автоматичний макет для обчислення фактичної висоти рядка
Яблуко: робота з самостійними розмірами осередків подання таблиці
За допомогою iOS 8 компанія Apple втілила більшу частину роботи, яку раніше вам довелося виконати перед iOS 8. Для того, щоб механізм саморозмірних комірок працював, спочатку потрібно встановити rowHeight
властивість на поданні таблиці постійною UITableViewAutomaticDimension
. Потім просто потрібно включити оцінку висоти рядків, встановивши estimatedRowHeight
властивість подання таблиці на ненульове значення, наприклад:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
Для цього потрібно надати в таблиці таблиці тимчасову оцінку / заповнювач для висот рядків комірок, які ще не відображаються на екрані. Потім, коли ці клітинки збираються прокручуватися на екрані, буде обчислена фактична висота рядка. Щоб визначити фактичну висоту для кожного ряду, подання таблиці автоматично запитує кожну клітинку, яка її висотаcontentView
повинна базувати на відомій фіксованій ширині подання вмісту (яка ґрунтується на ширині подання таблиці, за вирахуванням будь-яких додаткових речей, таких як індекс розділу або перегляд аксесуарів) та обмеження щодо автоматичного макета, які ви додали до перегляду вмісту і підпогляди комірки. Після того, як ця фактична висота комірки буде визначена, стара передбачувана висота рядка оновлюється новою фактичною висотою (і будь-які коригування змісту розміру / contentOffset перегляду таблиці виконуються відповідно до вас).
Взагалі кажучи, надана вами оцінка не повинна бути дуже точною - вона використовується лише для правильного розміру індикатора прокрутки у поданні таблиці, а перегляд таблиці добре коригує показник прокрутки для неправильних оцінок. прокручування комірок на екрані. Ви повинні встановити estimatedRowHeight
властивість у поданні таблиці (у viewDidLoad
або подібному) на постійне значення, яке є "середньою" висотою рядка. Тільки якщо висота ваших рядків має надзвичайну мінливість (наприклад, різняться на порядок), і ви помічаєте індикатор прокрутки «стрибки» під час прокрутки, якщо вам tableView:estimatedHeightForRowAtIndexPath:
потрібно зайнятися виконанням мінімального обчислення, необхідного для повернення точнішої оцінки для кожного рядка.
Підтримка iOS 7 (впровадження автоматичного розміру комірок)
3. Зробіть макет і пройдіть висоту комірки
По-перше, примірник зовнішнього екземпляра комірки таблиці перегляду, один екземпляр для кожного ідентифікатора повторного використання , який використовується строго для обчислень висоти. (Позаекранний, що означає посилання на комірку, зберігається у властивості / ivar на контролері перегляду та ніколи не повертається з ньогоtableView:cellForRowAtIndexPath:
за того, щоб перегляд таблиці фактично відображався на екрані.) Далі, комірка повинна бути налаштована з точним вмістом (наприклад, текстом, зображеннями тощо) що вона буде утримуватися, якщо вона відображатиметься у поданні таблиці.
Потім примусіть клітинку негайно розставити її підпогляди, а потім скористайтеся systemLayoutSizeFittingSize:
методом на UITableViewCell
, contentView
щоб дізнатися, яка потрібна висота комірки. Використовуйте UILayoutFittingCompressedSize
для отримання найменшого розміру, необхідного для вмісту всього вмісту комірки. Потім висоту можна повернути зtableView:heightForRowAtIndexPath:
методом делегування.
4. Використовуйте розрахункові висоти рядків
Якщо у вашому поданні таблиці є більше декількох десятків рядків, ви побачите, що вирішення обмежень щодо автоматичного макета може швидко завалити основну нитку при першому завантаженні подання таблиці, як tableView:heightForRowAtIndexPath:
це називається в кожному рядку при першому завантаженні ( щоб розрахувати розмір показника прокрутки).
Що стосується iOS 7, ви можете (і абсолютно слід) використовувати estimatedRowHeight
властивість у поданні таблиці. Для цього потрібно надати в таблиці таблиці тимчасову оцінку / заповнювач для висот рядків комірок, які ще не відображаються на екрані. Потім, коли ці клітинки збираються прокручувати на екрані, буде обчислена фактична висота рядка (за допомогою викликуtableView:heightForRowAtIndexPath:
), а розрахункова висота оновиться до фактичної.
Взагалі кажучи, надана вами оцінка не повинна бути дуже точною - вона використовується лише для правильного розміру індикатора прокрутки у поданні таблиці, а перегляд таблиці добре коригує показник прокрутки для неправильних оцінок. прокручування комірок на екрані. Ви повинні встановити estimatedRowHeight
властивість у поданні таблиці (у viewDidLoad
або подібному) на постійне значення, яке є "середньою" висотою рядка. Тільки якщо висота ваших рядків має надзвичайну мінливість (наприклад, різняться на порядок), і ви помічаєте індикатор прокрутки «стрибки» під час прокрутки, якщо вам tableView:estimatedHeightForRowAtIndexPath:
потрібно зайнятися виконанням мінімального обчислення, необхідного для повернення точнішої оцінки для кожного рядка.
5. (Якщо потрібно) Додайте кешування висоти рядків
Якщо ви зробили все вищезазначене і все ще виявляєте, що продуктивність неприпустимо повільна під час вирішення обмежень tableView:heightForRowAtIndexPath:
, вам, на жаль, потрібно буде застосувати кешування для висоти комірок. (Це підхід, запропонований інженерами Apple.) Загальна ідея полягає в тому, щоб механізм Autolayout вперше вирішив обмеження, а потім кешував обчислену висоту для цієї комірки і використовував кешоване значення для всіх майбутніх запитів на висоту цієї комірки. Трюк, звичайно, полягає в тому, щоб переконатися, що ви очистите кешовану висоту для комірки, коли щось трапиться, що може спричинити зміну висоти комірки - насамперед, це буде коли зміна вмісту цієї комірки або коли відбудуться інші важливі події (наприклад, користувач коригує повзунок розміру тексту «Динамічний тип»).
Загальний зразок коду iOS 7 (з великою кількістю соковитих коментарів)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline UILabels inside the
// -[layoutSubviews] method of the UITableViewCell subclass, or do it
// manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
Зразкові проекти
Ці проекти є повністю працюючими прикладами подань таблиць із змінною висотою рядків завдяки осередкам подання таблиці, що містять динамічний вміст у UILabels.
Ксамарін (C # /. NET)
Якщо ви використовуєте Xamarin, ознайомтеся з цим зразковим проектом, складеним @KentBoogaart .