Використання автоматичного макета в UITableView для динамічних макетів комірок та змінної висоти рядків


1501

Як ви використовуєте автоматичний макет в межах UITableViewCells у вигляді таблиці, щоб дозволити вміст і підвідділ кожної комірки визначати висоту рядка (сам / автоматично), зберігаючи продуктивність прокрутки?


Ось зразок коду у швидкій 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

Щоб зрозуміти більшість відповідей щодо наявності багатолінійного UILabel, потрібно повне розуміння того, як contentSizeі як preferredMaxLayoutWidthпрацювати. Дивіться тут . Це буде сказано, якщо ви правильно встановите свої обмеження, тоді вам це не потрібно, preferredMaxLayoutWidthі насправді це може створити несподівані результати.
Мед

Відповіді:


2386

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 .


1
Хоча це працює добре, я вважаю, що під оцінками потрібні розміри трохи (можливо, через різні проблеми округлення), і я повинен додати пару балів до кінцевої висоти, щоб весь мій текст вмістився всередині міток
DBD

5
@ Alex311 Дуже цікаво, дякую за надання цього прикладу. Я трохи провів тестування на своєму кінці і написав тут деякі коментарі: github.com/Alex311/TableCellWithAutoLayout/commit/…
smileyborg

3
Я настійно рекомендую кешувати клітинку для кожного типу ідентифікатора повторного використання комірки. Кожен раз, коли ви декеюєте на висоту, ви виймаєте клітинку з черги, яка не додається назад. Кешування може значно зменшити кількість викликів ініціалізатора для клітин вашої таблиці. Чомусь, коли я не кешував, кількість пам'яті, яка використовується, зростала, оскільки я прокручувала.
Alex311

1
Насправді розкадрування. Я не думаю, що це працює для прототипу комірок (принаймні, без повторної інстанції цілого ВК). Можливо, можливо, уникнути витоку взагалі, скасувавшись у ньому heightForRowAtIndexPath, утримуючи клітинку та повертаючи її наступного разу, коли cellForRowAtIndexPathбуде викликано.
nschum

2
Чи iOS8все ж рекомендується впровадження iOS9та iOS10чи існують нові підходи після опублікування цієї відповіді?
koen

175

Для iOS 8 вище це дуже просто:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableView.automaticDimension
}

або

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

Але для iOS 7 ключ розраховує висоту після автоматичного вимикання:

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height
}

Важливо

  • Якщо кілька рядків міток, не забувайте встановити numberOfLinesна 0.

  • Не забувайте label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

Повний код прикладу тут .


7
Я думаю, нам не потрібно реалізовувати функцію heightForRowAtIndexPath у випадку
OS8

Як зазначає @eddwinpaz в іншій відповіді, важливо, щоб обмеження, застосовані для блокування у висоту рядка, НЕ включали поля.
Річард

1
+1 для "Якщо мітки декількох рядків, не забудьте встановити числоOfLines на 0", це призвело до того, що мої клітини не мають динамічного розміру.
Ян-Фогельман

Називайте мене контрольним фріком, але навіть у дні iOS 11 мені все ще не подобається використовувати UITableViewAutomaticDimension. У минулому були якісь погані переживання з цим. Як такий, я зазвичай використовую перелічені тут рішення iOS7. Примітка Вільяма не забувати label.preferredMaxLayoutWidth тут врятувала мене.
Брайан Сачетта

Коли ми прокручуємо, мітка починає з’являтися у кількох рядках, а висота рядка також не збільшується
Karanveer Singh

93

Швидкий приклад змінної висоти UITableViewCell

Оновлено для Swift 3

Швидка відповідь Вільяма Ху - це добре, але це допомагає мені зробити кілька простих, але детальних кроків, коли навчитися робити щось вперше. Наведений нижче приклад - мій тестовий проект, коли я вчився робити UITableViewзмінну висоту комірок. Я грунтувався на цьому базовому прикладі UITableView для Swift .

Готовий проект повинен виглядати приблизно так:

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

Створіть новий проект

Це може бути лише програма Single View.

Додайте код

Додайте новий файл Swift до свого проекту. Назвіть це MyCustomCell. У цьому класі будуть розміщені торгові точки для представлень даних, які ви додаєте до своєї комірки в таблиці розмов. У цьому базовому прикладі у нас буде лише одна мітка у кожній комірці.

import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UILabel!
}

Ми підключимо цю розетку пізніше.

Відкрийте ViewController.swift і переконайтеся, що у вас є такий вміст:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

Важлива примітка:

  • Саме наступні два рядки коду (разом з автоматичним компонуванням) роблять можливою змінну висоту комірки:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension

Налаштуйте дошку

Додайте табличний вигляд до контролера перегляду та використовуйте автоматичний макет, щоб закріпити його на чотирьох сторонах. Потім перетягніть комірку перегляду таблиці на вигляд таблиці. І на клітинку Prototype перетягніть мітку. Використовуйте автоматичний макет, щоб прикріпити мітку до чотирьох країв перегляду вмісту осередку перегляду таблиці.

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

Важлива примітка:

  • Автоматичний макет працює разом із важливими двома рядками коду, про які я згадував вище. Якщо ви не використовуєте автоматичний макет, він не працюватиме.

Інші настройки IB

Ім'я та ідентифікатор спеціального класу

Виберіть комірку перегляду таблиці та встановіть призначений для користувача клас MyCustomCell(назва класу у файлі Swift, який ми додали). Також встановіть, що Ідентифікатор буде cell(той самий рядок, який ми використовували для cellReuseIdentifierв коді вище).

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

Нульові лінії для мітки

Встановіть кількість рядків 0у мітці. Це означає багаторядковість і дозволяє мітці змінити розмір себе залежно від вмісту.

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

Підключіть торгові точки

  • Перетягніть контроль із перегляду таблиці на дошці розкадрування до tableView змінної вViewController коді.
  • Зробіть те ж саме для мітки в комірці прототипу для myCellLabelзмінної вMyCustomCell класі.

Готово

Ви повинні мати можливість запустити свій проект зараз і отримати комірки зі змінною висотою.

Примітки

  • Цей приклад працює лише для iOS 8 та після нього. Якщо ви все ще потребуєте підтримки iOS 7, це не допоможе вам.
  • Ваші власні клітинки у ваших майбутніх проектах, ймовірно, матимуть більше, ніж одну мітку. Переконайтеся, що ви все зафіксували правильно, щоб автоматична компоновка могла визначити правильну висоту. Можливо, вам також доведеться використовувати вертикальний опір стиску та обійми. Докладніше про це див. У цій статті .
  • Якщо ви не закріплюєте передній і кінцевий (лівий і правий) краї, вам також може знадобитися встановити мітку preferredMaxLayoutWidthтак, щоб вона знала, коли слід обертати лінію. Наприклад, якщо ви додали обмеження по центру по горизонталі до мітки в проекті вище, а не закріплювали провідні та кінцеві кромки, вам потрібно буде додати цей tableView:cellForRowAtIndexPathметод до методу:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Дивись також


Хіба насправді не краще встановити preferredMaxLayoutWidthпроти клітини contentSize? Таким чином, якщо у вас є accessoryView або його провели для редагування, це все-таки буде враховано?
Мед

@Honey, ти дуже добре можеш мати рацію. Я не тримався на iOS близько року, і я занадто іржавий, щоб відповісти вам добре зараз.
Сурагч

65

Я перетворив рішення iOS7 @ smileyborg в категорію

Я вирішив перетворити це розумне рішення від @smileyborg в UICollectionViewCell+AutoLayoutDynamicHeightCalculationкатегорію.

Категорія також виправляє проблеми, викладені у відповіді @ wildmonkey (завантаження клітинки від кнопки та systemLayoutSizeFittingSize:повернення CGRectZero)

Він не враховує кешування, але відповідає моїм потребам зараз. Не соромтеся копіювати, вставляти та зламати його.

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
 */
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

/**
 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *
 *  @param name Name of the nib file.
 *
 *  @return collection view cell for using to calculate content based height
 */
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

/**
 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *
 *  @param block Render the model data to your UI elements in this block
 *
 *  @return Calculated constraint derived height
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

/**
 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;

@end

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

Приклад використання:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

На щастя, нам не доведеться робити цей джаз в iOS8, але це є наразі!


2
Ви повинні просто вміти використовувати: [YourCell new]і використовувати це як манекен. Поки у вашому екземплярі запускається код побудови коду обмеження, і ви запускаєте програмування макета програмно, вам слід добре піти.
Адам Уейт

1
Дякую! Це працює. Ваша категорія чудова. Саме це змусило мене зрозуміти, що ця техніка працює і з цим UICollectionViews.
Рікардо Санчес-Саес

2
Як би ви це зробили, використовуючи комірку-прототип, визначену на раскадровці?
Девід Поттер

57

Ось моє рішення:

Вам потрібно сказати TableViewпопереднє, estimatedHeightперш ніж воно завантажить вигляд. Інакше він не зможе поводитись так, як очікувалося.

Ціль-С

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}

Оновлення до Swift 4.2

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}

2
Налаштування автоматичної розкладки разом із цим кодом, доданим у viewDidLoad, зробила свою справу.
Брюс

1
але що робити, якщо estimatedRowHeightзмінюється рядок за рядком? я повинен перевищувати чи оцінювати? використовувати мінімум чи максимум висоти, яку я використовую tableView?
János

1
@ János, це точка рядкаHeight. Для цього потрібно вести себе так, як очікувалося, потрібно використовувати обмеження без поля та вирівняти об’єкти TableViewCell. і я припускаю, що ви використовуєте UITextView, тому все-таки вам потрібно видалити autoscroll = false, інакше він буде підтримувати висоту і відносну висоту, як і раніше, діяти.
Едвін Пас

1
Це, безумовно, найбільш надійне рішення. Не хвилюйтеся estimatedRowHeight, це в основному впливає на розмір смуги прокрутки, ніколи не висоту фактичних комірок. Будьте жирними у вибраній висоті: це вплине на анімацію вставки / видалення.
SwiftArchitect

Ось зразок коду у швидкій 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

47

Рішення, запропоноване @smileyborg, майже ідеальне. Якщо у вас є власна комірка і ви хочете одну або більше UILabelз динамічними висотами, метод systemLayoutSizeFittingSize у поєднанні з функцією AutoLayout повертається, CGSizeZeroякщо ви не перемістите всі обмеження вашої комірки з комірки до її contentView (як запропонував тут @TomSwift. Як змінити розмір нагляду до підходять усі підпогляди з автоматичним розгортанням? ).

Для цього вам потрібно вставити наступний код у вашу власну реалізацію UITableViewCell (завдяки @Adrian).

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

Змішування @smileyborg відповіді з цим повинно працювати.


systemLayoutSizeFittingSizeпотрібно викликати в contentView, а не в комірці
onmyway133,

25

Досить важливий дімчак, який я щойно наштовхнувся на пост як відповідь.

@ smileyborg відповідь здебільшого правильна. Однак якщо у вас є який-небудь код у layoutSubviewsметоді вашого користувацького класу комірок, наприклад, встановивши preferredMaxLayoutWidth, він не буде запускатися з цим кодом:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

Це збентежило мене деякий час. Тоді я зрозумів, що це тому, що вони викликають лише layoutSubviews на contentView, а не саму клітинку.

Мій робочий код виглядає приблизно так:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

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

Ще одна порада, якщо ви використовуєте підкласи клітинок, де ви встановлюєте такі речі preferredMaxLayoutWidth. Як зазначає @smileyborg, "у комірці перегляду таблиці ще не було закріплено її ширину на ширині подання таблиці". Це правда, і проблеми, якщо ви робите свою роботу в підкласі, а не в контролері перегляду. Однак ви можете просто встановити кадр комірки в цей момент, використовуючи ширину таблиці:

Наприклад, у розрахунку на висоту:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(Мені трапляється кешувати цю конкретну клітинку для повторного використання, але це не має значення).


21

У випадку, якщо люди все ще мають проблеми з цим. Я написав швидку публікацію в блозі про використання функції Autolayout з використанням UITableViews, що використовує функцію автоматичного вимикання для динамічних висот комірок , а також компонент з відкритим кодом, щоб зробити це більш абстрактним та легшим у виконанні. https://github.com/Raizlabs/RZCellSizeManager


19

Поки ваш макет у вашій комірці хороший.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

Оновлення: Вам слід використовувати динамічний розмір, введений в iOS 8.


Це працює для мене на iOS7, він зараз OK , щоб зателефонувати tableView:cellForRowAtIndexPath:в tableView:heightForRowAtIndexPath:зараз?
Мурашки

Ok так що це не працює, але коли я дзвоню systemLayoutSizeFittingSize:в tableView:cellForRowAtIndexPath:і кешувати результат тоді , а потім використовувати його в tableView:heightForRowAtIndexPath:це працює добре, поки обмеження налаштовані правильно, звичайно!
Мурашки

Це працює лише в тому випадку, якщо ви використовуєте dequeueReusableCellWithIdentifier: замість dequeueReusableCellWithIdentifier: forIndexPath:
Antoine

1
Я дійсно не думаю, що виклик tableView: cellForRowAtIndexPath: безпосередньо - хороший спосіб.
Ітачі

19

(для Xcode 8.x / Xcode 9.x читати внизу)

Остерігайтеся наступної проблеми в Xcode 7.x, яка може бути джерелом плутанини:

Інтерфейс Builder не належним чином обробляє налаштування комірок з автоматичним розміром. Навіть якщо ваші обмеження абсолютно справедливі, IB все одно скаржиться та надасть вам заплутані пропозиції та помилки. Причина полягає в тому, що IB не бажає змінювати висоту рядка, як диктують ваші обмеження (щоб комірка облягала ваш вміст). Натомість він зберігає фіксовану висоту рядка і починає пропонувати змінити свої обмеження, які слід ігнорувати .

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

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

Тепер якщо ви зміните розмір шрифту (у цьому прикладі я змінюю розмір шрифту етикетки для опису з 17,0 на 18,0).

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

Оскільки розмір шрифту збільшився, тепер мітка хоче займати 3 ряди (до цього вона займала 2 ряди).

Якби Interface Builder працював так, як очікувалося, він змінив би розмір висоти комірки для розміщення нової висоти мітки. Однак насправді трапляється те, що IB відображає червоний значок помилки автоматичного макета та пропонує вам змінити пріоритети обіймів / стиснення.

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

Ви повинні ігнорувати ці попередження. Що можна зробити * замість цього - це вручну змінити висоту рядка на (виберіть Стільниця> Інспектор розміру> Висота рядка).

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

Я змінював цю висоту одним натисканням кнопки (за допомогою крок вгору / вниз), поки помилки червоної стрілки не зникли! (ви дійсно отримаєте жовті попередження. Після цього просто продовжуйте робити «оновлення кадрів», все повинно працювати).

* Зауважте, що вам фактично не доводиться вирішувати ці червоні помилки або жовті попередження в Interface Builder - під час виконання роботи все буде працювати правильно (навіть якщо IB показує помилки / попередження). Просто переконайтесь, що під час виконання в журналі консолі ви не отримуєте жодних помилок автоматичної розробки.

Насправді намагатися завжди оновлювати висоту рядка в ІБ - це дуже прикро і іноді близько до неможливого (через дробові значення).

Щоб запобігти набридливим попередженням / помилкам ІБ, ви можете обрати перегляди та ввійти Size Inspectorдля Ambiguityвибору властивостіVerify Position Only

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


Xcode 8.x / Xcode 9.x, здається, (іноді) робить інакше, ніж Xcode 7.x, але все-таки неправильно. Наприклад, навіть коли compression resistance priority/ hugging priorityвстановлено необхідне значення (1000), Інтерфейс Builder може розтягнути або обрізати мітку, щоб підходити до комірки (замість зміни розміру висоти комірки, щоб розміститися навколо мітки). І в такому випадку він може навіть не показувати жодних попереджень або помилок AutoLayout. Або іноді він робить саме те, що робив Xcode 7.x, описаний вище.


привіт, чи можна надати динамічну висоту для клітини, що має табличний перегляд з коміркою з динамічним вмістом комірки.
Jignesh B

18

Щоб встановити автоматичний розмір для висоти рядка та передбачуваної висоти рядка, переконайтесь, що слід виконати наступні кроки, щоб автоматичний параметр був ефективним для компонування висоти комірки / рядка.

  • Призначення та реалізація табличного перегляду dataSource та делегат
  • Призначити UITableViewAutomaticDimension до рядкаВисота та Оцінка РоуВест
  • Реалізація методів delegate / dataSource (тобто heightForRowAtі повернути їй значення UITableViewAutomaticDimension)

-

Мета C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return UITableViewAutomaticDimension;
}

Швидкий:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

Для екземпляра мітки в UITableviewCell

  • Встановити кількість рядків = 0 (& режим розриву рядка = обрізати хвіст)
  • Встановіть всі обмеження (вгорі, внизу, праворуч зліва) щодо його нагляду / контейнера комірок.
  • Необов’язково : встановіть мінімальну висоту для етикетки, якщо ви хочете, щоб мінімальна вертикальна площа була покрита міткою, навіть якщо немає даних.

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

Примітка . Якщо у вас є більше однієї мітки (UIElements) з динамічною довжиною, яку слід відрегулювати відповідно до її розміру вмісту: Відрегулюйте "Пріоритет обіймання вмісту та стійкість до стиснення" для міток, які потрібно розгорнути / стиснути з більшим пріоритетом.


1
Дякую, здається, чітке і просте рішення, але у мене є проблема, що я не використовую мітки, а перегляд тексту, тому мені потрібно, щоб висота рядка збільшувалася в міру додавання даних. Моя проблема тоді полягає в тому, щоб передати інформацію до висотиForRowAt. Я можу виміряти висоту моїх переглядів тексту, але тепер потрібно змінити висоту рядка. Буду вдячний за допомогу, будь ласка
Джеремі Ендрюс

@JeremyAndrews Звичайно, допоможе вам. Підніміть своє запитання за допомогою вихідного коду, який ви спробували, і детально розкрийте проблему.
Крунал

Це працює для мене без реалізації tableView: heightForRowджерела даних.
Хеман

@Hemang - Оскільки iOS 11+ працює без нього tableView: heightForRow. (для iOS 10 tableView: heightForRow- потрібно)
Krunal

1
@Hemang - Рішення залежить від точного визначення запиту. Ось загальне рішення для вашого запиту. Поставте умову всередину tableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
Крунал

16

Як @ Bob-Spryn я зіткнувся з досить важливою дією, що я публікую це як відповідь.

Я деякий час боровся з відповіддю @ smileyborg . Я отримав натхнення, якщо ви визначили комірку-прототипу в IB з додатковими елементами ( UILabels, UIButtonsтощо) в ІБ, коли ви інстанціюєте цю клітинку [ [YourTableViewCellClass alloc] init]вона не інстанціює всі інші елементи в цій клітині, якщо ви не написаний код для цього. (У мене був подібний досвід ізinitWithStyle .)

Для того, щоб загадувальний аркуш придумував усі додаткові елементи отримувати вашу клітинку [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](Не [tableView dequeueReusableCellWithIdentifier:forIndexPath:]тому, що це спричинить цікаві проблеми.) Коли ви це зробите, всі елементи, визначені у ІБ, будуть ініційовані.


15

Динамічна висота стільникового виду та автоматична компонування

Хороший спосіб вирішити проблему з автоматичною розкладкою розкадровки:

- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
  static RWImageCell *sizingCell = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
  });

  [sizingCell setNeedsLayout];
  [sizingCell layoutIfNeeded];

  CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
  return size.height;
}

1
Це вже широко висвітлено у прийнятій відповіді на це питання.
smileyborg

2
Так, я знаю ... але я не хотів використовувати PureLayout, і "трюк" dispatch_once мені дуже допоміг, опрацювавши це лише за допомогою "Розгортки".
Мохназм

13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

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


13

Ще одне "рішення": пропустити все це розчарування і замість цього скористатися UIScrollView, щоб отримати результат, схожий на UITableView.

Це було болісне «рішення» для мене, після того, як я вклав буквально 20+ дуже неприємних годин, намагаючись створити щось на кшталт того, що запропонував усміхборг і провалився протягом багатьох місяців, і три версії версій App Store.

Я вважаю, що якщо вам дійсно потрібна підтримка iOS 7 (для нас це важливо), то технологія є занадто крихкою, і ви намагаєтеся витягнути волосся. І що UITableView є повною надмірною кількістю, якщо ви не використовуєте деякі розширені функції редагування рядків та / або дійсно не потребуєте підтримки 1000+ "рядків" (у нашому додатку це реально ніколи не більше 20 рядків).

Додатковий бонус полягає в тому, що код стає шалено простим проти всіх делегатських лайна і назад, і назад, які поставляються з UITableView. Це просто один цикл коду в viewOnLoad, який виглядає елегантно і простий в управлінні.

Ось кілька порад, як це зробити:

  1. Використовуючи або аркуш розгортки, або файл nib, створити ViewController та пов'язаний з ним перегляд кореня.

  2. Перетягніть на UIScrollView на свій кореневий вигляд.

  3. Додайте обмеження верхнього, нижнього, лівого та правого обмежень у подання верхнього рівня, щоб UIScrollView заповнив увесь вигляд кореня.

  4. Додайте UIView всередині UIScrollView і називайте його "контейнер". Додайте верхнє, нижнє, ліве та праве обмеження до UIScrollView (його батьківського). KEY TRICK: Також додайте обмеження "Рівні ширини", щоб з'єднати UIScrollView і UIView.

    ПРИМІТКА. Ви отримаєте помилку "Перегляд прокрутки має неоднозначну висоту вмісту прокрутки", і що ваш UIView контейнера повинен мати висоту 0 пікселів. Здається, жодна помилка не має значення при запуску програми.

  5. Створіть файли та контролери для кожної вашої "комірки". Використовуйте UIView not UITableViewCell.

  6. У вашому кореневому ViewController ви по суті додаєте всі "рядки" до UIView контейнера і програмно додаєте обмеження, що зв'язують їхній лівий і правий край з видом контейнера, їхні верхні краї або з вершиною перегляду контейнера (для першого елемента), або з попереднього клітина. Потім підключіть кінцеву комірку до дна контейнера.

Для нас кожен "рядок" знаходиться у файлі nib. Отже, код виглядає приблизно так:

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {

        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

А ось код для UITools.addViewToTop:

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)

        //Set left and right constraints so fills full horz width:

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))

        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

Єдиний "gotcha", який я знайшов при такому підході до цих пір, - це те, що UITableView має гарну особливість "плаваючих" заголовків розділів у верхній частині подання під час прокрутки. Наведене вище рішення не зробить цього, якщо ви не додасте більше програмування, але для нашого конкретного випадку ця функція не була на 100% суттєвою, і ніхто не помітив, коли вона пішла.

Якщо ви хочете розділити між вашими клітинками, просто додайте UIView в висоту 1 пікселя внизу вашої власної "комірки", що виглядає як дільник.

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

TableView показує деякі порожні рядки та роздільники під вашим вмістом, якщо він не заповнює весь екран там, де цього рішення немає. Але особисто я вважаю за краще, якби тих порожніх рядків все одно не було - зі змінною висотою комірки вона завжди виглядала "баггі" для мене, як би там не було порожніх рядків.

Ось сподіваюсь, що хтось із інших програмістів читає мою публікацію ПЕРЕД витрачанням 20+ годин, намагаючись розібратися з табличним переглядом у власному додатку. :)


11

Мені довелося використовувати динамічні представлення (параметри перегляду та обмеження за кодом), і коли я хотів встановити бажану ширину мітки MaxLayoutWidth, було 0, тож у мене неправильна висота комірки.

Потім я додав

[cell layoutSubviews];

перед виконанням

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

Після цього ширина ярлика була такою, як очікувалося, і динамічна висота підраховувалась правильно.


9

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

1) Встановіть нижнє обмеження субпрогляду рівним cell.contentView за вирахуванням необхідної підкладки. Не встановлюйте обмеження для самої комірки або cell.contentView.

2) Встановіть або властивість tableView, rowHeightабо tableView:heightForRowAtIndexPath:значення UITableViewAutomaticDimension.

3) Встановіть або властивість tableView, estimatedRowHeightабоtableView:estimatedHeightForRowAtIndexPath: найкраще вгадайте висоту.

Це воно.


7

Якщо ви плануєте програматизувати програмно, ось що слід врахувати для iOS 10 за допомогою якорів у Swift.

Існує три правила / кроки

НОМЕР 1: встановіть ці два властивості перегляду таблиці на viewDidLoad, перший - вказує на табличний перегляд, який повинен очікувати динамічних розмірів на їх клітинках, другий - просто дозволити додатку обчислювати розмір індикатора прокрутки, тому це допомагає для виконання.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

НОМЕР 2: Це важливо, що вам потрібно додати підпогляди до contentView комірки, а не для перегляду, а також використовувати її макети розмітки, щоб закріпити підгляди вгорі та внизу, це робочий приклад того, як це зробити.

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

Створіть метод, який додасть підпогляди та виконає макет, викликайте його методом init.

ЧИСЛО 3: НЕ НАЗВАЙТЕ МЕТОД:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

Якщо ви це зробите, ви скасуєте свою реалізацію.

Дотримуйтесь цих 3 правил щодо динамічних комірок у таблиці.

ось робоча реалізація https://github.com/jamesrochabrun/MinimalViewController


у Swift 5 UITableViewAutomaticDimension перейменовано на UITableView.automaticDimension
Джеймс Рочабрун

4

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

Виправлення "нібито" згадується прийнятою відповіддю та кількома іншими відповідями. Вам просто потрібно додати

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Відповідь Сура вважаю найбільш повною і стислою , отже, не заплутаною.

Хоча не пояснюйте, для чого потрібні ці зміни. Давайте це зробимо.

Додайте наступний код до проекту.

import UIKit

class ViewController: UIViewController {

    lazy var label : UILabel = {
        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.backgroundColor = .red
        lbl.textColor = .black
        return lbl
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // step0: (0.0, 0.0)
        print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step1: (29.0, 20.5)
        label.text = "hiiiii"
        print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step2: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints"
        print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step3: (992.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
        print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step4: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
        print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step5: (328.0, 61.0)
        label.numberOfLines = 0
        print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step6: (98.5, 243.5)
        label.preferredMaxLayoutWidth = 100
        print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")

        setupLayout()
    }
    func setupLayout(){
        view.addSubview(label)
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
}

Зауважте, що я не додав жодних обмежень щодо розміру . Я додав лише обмеження centerX, centerY. Але все ж етикетка буде розміщена правильно, чому?

Через contentSize.

Щоб краще обробити цю процедуру, спочатку продовжте step0, а потім прокоментуйте кроки 1-6. Нехай setupLayout()залишиться. Поспостерігайте за поведінкою.

Потім відмініть крок1 та спостерігайте.

Потім відмініть крок 2 та спостерігайте.

Робіть це, поки ви не коментуєте всі 6 кроків і не спостерігали за їх поведінкою.

Що можна зробити з усього цього? Які фактори можуть змінити contenSize?

  1. Довжина тексту: Якщо у вас довший текст, то ширина вашого intrinsicContentSize збільшиться
  2. Розриви рядків: якщо ви додали\n то ширина intrinsicContentSize буде максимальною шириною всіх рядків. Якщо один рядок містить 25 символів, інший - 2 символи, а інший - 21 символ, то ваша ширина буде обчислена на основі 25 символів
  3. Кількість дозволених рядків: Ви повинні встановити значення numberOfLinesв 0іншому випадку, у вас не буде декількох рядків. Ви numberOfLinesбудете регулювати висоту intrinsicContentSize
  4. Внесення змін: Уявіть, що виходячи з тексту, ширина вашого intrinsicContentSize була, 200а висота - 100, але ви хотіли обмежити ширину контейнера мітки, що ви збираєтеся робити? Рішення - встановити його на потрібну ширину. Ви робите це, встановивши, preferredMaxLayoutWidthщоб 130ваш новий intrinsicContentSize матиме ширину приблизно 130. Висота, очевидно, буде більше, ніж 100тому, що вам потрібно більше ліній. Це сказано, якщо ваші обмеження встановлені правильно, тоді вам взагалі не потрібно буде використовувати це! Більше про це дивіться у цій відповіді та її коментарі. Використовувати його потрібно лише в тому preferredMaxLayoutWidthвипадку, якщо у вас немає обмежень, що обмежують ширину / висоту, як можна сказати "не загортайте текст, якщо він не перевищуєpreferredMaxLayoutWidth". Але зі 100% впевненістю, якщо ви встановите провідний / трейлінг і numberOfLinesна короткий короткий сюжет, більшість відповідей тут, які рекомендують використовувати, - НЕЗАКОМНО! Вам це не потрібно. Потрібність - це знак того, що ваші обмеження встановлені неправильно чи що у вас просто немає обмежень0 той ви добре!

  5. Розмір шрифту: Також врахуйте, що якщо ви збільшите розмір шрифту, то висота intrinsicContentSize збільшиться. Я цього не показав у своєму коді. Ви можете спробувати це самостійно.

Повернімося до прикладу tableViewCell:

Все, що вам потрібно зробити, це:

  • встановити numberOfLinesна0
  • правильно обмежуйте мітку до полів / країв
  • Не потрібно встановлювати preferredMaxLayoutWidth.

1

У моєму випадку мені потрібно створити власну комірку із зображенням, яке надходить із сервера і може бути будь-якої ширини та висоти. І два ярлики UIL з динамічним розміром (ширина та висота)

Я домігся того ж тут у своїй відповіді з авторозміткою та програмно:

В основному, відповідь @smileyBorg допомогла, але systemLayoutSizeFittingSize для мене ніколи не працював. У моєму підході:

1. Немає використання властивості автоматичного обчислення висоти рядків. 2.Не використовувати розрахункової висоти 3.Не потрібно зайвих оновленьКонстрицій. 4.Не використовувати автоматичну бажану максимальну ширину макета. 5. Немає використання systemLayoutSizeFittingSize (повинен мати використання, але не працює для мене, я не знаю, що це робить всередині), а натомість мій метод - (float) getViewHeight працює, і я знаю, що він робить всередині.

Чи можливо мати різну висоту в комірці UITableView, коли я використовую кілька різних способів відображення комірки?


1

У моєму випадку прокладка відбулася через висоту sectionHeader та sectionFooter, де дошка розкадрування дозволила мені змінити її до мінімуму 1. Отже, у методі viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

1

Я щойно зробив кілька невірних спроб і помилок з двома значеннями rowHeightі, estimatedRowHeightі я просто подумав, що це може дати деяку інформацію про налагодження:

Якщо ви встановите їх як АБО лише встановити, estimatedRowHeightви отримаєте бажану поведінку:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

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

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


Якщо ви встановите лише рядокHeight, тобто зробіть лише:

tableView.rowHeight = UITableViewAutomaticDimension

ваш кінцевий результат не буде бажаним:

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


Якщо ви встановите значення estimatedRowHeight1 або менше, ви вийдете з ладу незалежно від rowHeight.

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

Я зіткнувся з таким повідомленням про помилку:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type
NSException

1

Що стосується прийнятої відповіді від @smileyborg, я знайшов

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

бути ненадійним у деяких випадках, коли обмеження неоднозначні. Краще змусити двигун компонування обчислити висоту в одному напрямку, використовуючи категорію помічників на UIView нижче:

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

Де w: ширина огляду таблиці


0

Просто додайте ці дві функції у свій контролер перегляду, це вирішить вашу проблему. Тут список - це рядковий рядок, який містить рядок кожного рядка.

 func tableView(_ tableView: UITableView, 
   estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])

    return (tableView.rowHeight) 
}

func calculateHeight(inString:String) -> CGFloat
{
    let messageString = input.text
    let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]

    let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)

    let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)

    let requredSize:CGRect = rect
    return requredSize.height
}

-1
swift 4

    @IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var tableView: UITableView!
    private var context = 1
 override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
    }
  // Added observer to adjust tableview height based on the content

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &self.context{
            if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
                print("-----")
                print(size.height)
                tableViewHeightConstraint.constant = size.height + 50
            }
        }
    }

//Remove observer
 deinit {

        NotificationCenter.default.removeObserver(self)

    }

-1

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

PS. Код для генерування об'єкта комірки може бути визначений в іншому способі для виклику іншого методу делегата комірок перегляду таблиці.


-3

UITableView.automaticDimension можна встановити через Interface Builder:

Xcode> Розкладка> Інспектор розмірів

Осередок таблиці таблиці> Висота рядка> Автоматично

Інспектор розмірів


-4

ще одне рішення iOs7 + iOs8 у Swift

var cell2height:CGFloat=44

override func viewDidLoad() {
    super.viewDidLoad()
    theTable.rowHeight = UITableViewAutomaticDimension
    theTable.estimatedRowHeight = 44.0;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
    cell2height=cell.contentView.height
    return cell
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height
    }
}

Примітка: systemLayoutSizeFittingSize не працює в моєму випадку
djdance

висота комірки не є правильною cellForRowAtIndexPath, клітина ще не викладена на даний момент.
Андрій Черненко

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