Вопрос: Использование автоматической компоновки в UITableView для динамических раскладок ячеек и переменных высот строк


Как вы используете автоматический макет внутри UITableViewCells в представлении таблицы, чтобы содержимое и подсечки каждой ячейки определяли высоту строки (сама / автоматически) при сохранении плавной прокрутки?


1338


источник


Ответы:


TL; DR: Не нравится читать? Подходите прямо к образцам проектов GitHub:

Концептуальное описание

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

1. Настройка и добавление ограничений

В вашей UITableViewCellподкласс, добавьте ограничения, так что подвыборы ячейки имеют свои ребра, прикрепленные к краям ячейки contentView (что наиболее важно к верхним и нижним краям). ПРИМЕЧАНИЕ: не подключайте субвью к самой ячейке; только к ячейкам contentView! Позвольте внутреннему размеру содержимого этих подзонов управлять высотой представления содержимого ячейки таблицы, убедившись, что сопротивление сжатию содержимого а также обнимание содержимого ограничения в вертикальном измерении для каждого подсмотра не переопределяются ограничениями с более высоким приоритетом, которые вы добавили. ( А? Кликните сюда. )

Помните, что идея состоит в том, чтобы подсмотры ячеек были связаны вертикально с содержимым содержимого ячейки, чтобы они могли «оказывать давление» и увеличивать охват содержимого до их соответствия. Используя примерную ячейку с несколькими подзонами, вот наглядная иллюстрация того, что некоторые (не все!) ваших ограничений нужно будет выглядеть так:

Example illustration of constraints on a table view cell.

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

Правильность ваших ограничений - это, безусловно, самая сложная и самая важная часть получения динамических высот ячеек, работающих с Auto Layout. Если вы допустили ошибку здесь, это может помешать всему остальному работать, так что не спешите! Я рекомендую настроить свои ограничения в коде, потому что вы точно знаете, какие ограничения добавляются туда, и гораздо легче отлаживать, когда что-то идет не так. Добавление ограничений в код может быть столь же простым и значительно более мощным, чем Interface Builder с использованием якорей компоновки или одним из фантастических API-интерфейсов с открытым исходным кодом, доступным в GitHub.

  • Если вы добавляете ограничения в код, вы должны сделать это один раз изнутри updateConstraintsметод вашего подкласса UITableViewCell. Обратите внимание, что updateConstraintsмогут быть вызваны более одного раза, поэтому, чтобы не добавлять одни и те же ограничения более одного раза, обязательно заверните свой код с добавлением ограничений в пределах updateConstraintsв проверке на булево свойство, такое как didSetupConstraints(который вы устанавливаете в YES после запуска кода с добавлением ограничения). С другой стороны, если у вас есть код, который обновляет существующие ограничения (например, constantсвойство на некоторых ограничениях), поместите это в updateConstraintsно вне проверки для didSetupConstraintsпоэтому он может запускаться каждый раз при вызове метода.

2. Определение уникальных идентификаторов повторного использования ячеек таблицы.

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

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

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

  • Не добавляйте ячейки с совершенно разными наборами ограничений в один и тот же пул повторного использования (т. Е. Используйте один и тот же идентификатор повторного использования), а затем попытайтесь удалить старые ограничения и установите новые ограничения с нуля после каждого удаления. Внутренний механизм автоматической компоновки не предназначен для обработки масштабных изменений ограничений, и вы увидите серьезные проблемы с производительностью.

Для iOS 8 - Self-Sizing Cells

3. Включение оценки высоты строки

Чтобы включить ячейки просмотра таблицы размеров, вы должны установить вид таблицы   Свойство rowHeight для UITableViewAutomaticDimension. Вы также должны   присвойте значение свойству measuredRowHeight. Как только оба   эти свойства заданы, система использует автоматический макет для вычисления   фактическая высота строки

Яблоко: Работа с ячейками просмотра таблицы размеров

В 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должна основываться на известной фиксированной ширине представления контента (которая основана на ширине табличного представления, минус любые дополнительные вещи, такие как индекс раздела или вспомогательный вид) и ограничения автоматического макета, которые вы добавили в контентное представление и подпрограммы ячейки , После определения фактической высоты ячейки старая оцененная высота для строки обновляется с новой фактической высотой (и любые корректировки в contentSize / contentOffset для табличного представления создаются по мере необходимости для вас).

Вообще говоря, предоставленная вами оценка не обязательно должна быть очень точной - она ​​используется только для правильного размера индикатора прокрутки в представлении таблицы, и в представлении таблицы хорошая работа по настройке индикатора прокрутки для неправильных оценок, поскольку вы прокручивать ячейки на экране. Вы должны установить estimatedRowHeightсвойство на представлении таблицы (в viewDidLoadили подобное) к постоянному значению, которое является «средней» высотой строки. Только если высота ваших строк имеет экстремальную изменчивость (например, различаются на порядок), и вы заметите, что индикатор прокрутки «прыгает», когда вы прокручиваете, tableView:estimatedHeightForRowAtIndexPath:чтобы выполнить минимальный расчет, необходимый для возврата более точной оценки для каждой строки.

Для поддержки iOS 7 (для самой автоматической настройки размеров ячеек)

3. Сделайте прокладку макета и получите высоту ячейки

Во-первых, создайте экземпляр внеэкранного экземпляра ячейки представления таблицы, один экземпляр для каждого идентификатора повторного использования , который используется строго для расчета высоты. (Offscreen означает, что ссылка на ячейку хранится в свойстве / ivar на контроллере представления и никогда не возвращается из tableView:cellForRowAtIndexPath:для представления таблицы для фактического рендеринга на экране.) Далее ячейка должна быть настроена с точным содержимым (например, текстом, изображениями и т. д.), которое оно будет удерживать, если оно должно отображаться в представлении таблицы.

Затем принудительно соберите ячейку, чтобы сразу разместить свои подпункты, а затем использовать systemLayoutSizeFittingSize:метода на UITableViewCell«s contentViewчтобы узнать, какова требуемая высота ячейки. использование UILayoutFittingCompressedSizeчтобы получить наименьший размер, необходимый для соответствия всем содержимым ячейки. Затем высота может быть возвращена из tableView:heightForRowAtIndexPath:делегата.

4. Используйте приблизительные высоты строк

Если в вашем представлении таблицы содержится более нескольких десятков строк, вы обнаружите, что выполнение решения ограничения автоматического макета может быстро переманить основной поток при первой загрузке представления таблицы, поскольку tableView:heightForRowAtIndexPath:вызывается для каждой строки при первой загрузке (чтобы рассчитать размер индикатора прокрутки).

Начиная с iOS 7, вы можете (и абсолютно должны) использовать estimatedRowHeightсвойство на представлении таблицы. Это означает, что табличное представление содержит временную оценку / местозаполнитель для высоты строк ячеек, которые еще не отображаются на экране. Затем, когда эти ячейки собираются прокручивать экран, фактическая высота строки будет вычисляться (путем вызова tableView:heightForRowAtIndexPath:), а расчетная высота обновляется с фактической.

Вообще говоря, предоставленная вами оценка не обязательно должна быть очень точной - она ​​используется только для правильного размера индикатора прокрутки в представлении таблицы, и в представлении таблицы хорошая работа по настройке индикатора прокрутки для неправильных оценок, поскольку вы прокручивать ячейки на экране. Вы должны установить estimatedRowHeightсвойство на представлении таблицы (в viewDidLoadили подобное) к постоянному значению, которое является «средней» высотой строки. Только если высота ваших строк имеет экстремальную изменчивость (например, различаются на порядок), и вы заметите, что индикатор прокрутки «прыгает», когда вы прокручиваете, tableView:estimatedHeightForRowAtIndexPath:чтобы выполнить минимальный расчет, необходимый для возврата более точной оценки для каждой строки.

5. (если необходимо) Добавить кеширование высоты строки

Если вы сделали все вышеизложенное и все еще находите, что производительность неприемлемо медленна при решении ограничений в tableView:heightForRowAtIndexPath:, вам, к сожалению, нужно будет реализовать некоторое кэширование для высоты ячейки. (Это подход, предложенный инженерами Apple.) Основная идея заключается в том, чтобы позволить автоматическому механизму компоновки разрешать ограничения в первый раз, затем кэшировать рассчитанную высоту для этой ячейки и использовать кешированное значение для всех будущих запросов для высоты этой ячейки. Трюк, конечно же, заключается в том, чтобы убедиться, что вы очищаете кешированную высоту для ячейки, когда что-то происходит, что может привести к изменению высоты ячейки - в первую очередь это произойдет, когда содержимое этой ячейки изменится или когда произойдут другие важные события (например, пользовательская настройка ползунок размера текста динамического типа).

Общий код образца 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 multi-line 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.

Xamarin (C # /. NET)

Если вы используете Xamarin, проверьте это образец проекта составленный @KentBoogaart ,


2251



Для IOS8 это очень просто:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

ИЛИ

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

Но для IOS7 ключ вычисляет высоту после автовыключения,

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)

Полный примерный код Вот ,


118



Быстрый пример переменной высоты UITableViewCell

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

Ответ Уильяма Ху Стрита хорош, но он помогает мне иметь простые, но подробные шаги, когда вы научитесь что-то делать в первый раз. Пример ниже - это мой тестовый проект, учась делать UITableViewс переменными высотами ячеек. Я основывал его на этот базовый пример UITableView для Swift ,

Готовый проект должен выглядеть так:

enter image description here

Создать новый проект

Это может быть просто приложение с одним представлением.

Добавить код

Добавьте в проект новый файл 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
    

Настройка раскадровки

Добавьте представление таблицы в контроллер просмотра и используйте автоматический макет, чтобы прикрепить его к четырем сторонам. Затем перетащите элемент Table View на представление таблицы. И в ячейку Prototype перетащите ярлык. Используйте автоматическую компоновку, чтобы привязать метку к четырем краям представления содержимого ячейки Table View.

enter image description here

Важная заметка:

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

Другие настройки IB

Пользовательское имя класса и идентификатор

Выберите ячейку «Просмотр таблицы» и установите для настраиваемого класса MyCustomCell(имя класса в файле Swift, который мы добавили). Также установите Идентификатор cell(та же строка, что и для cellReuseIdentifierв приведенном выше коде.

enter image description here

Нулевые строки для метки

Задайте количество строк для 0в вашем ярлыке. Это означает, что многострочная линия и позволяет этикетке изменять размеры на основе ее содержимого.

enter image description here

Подключите розетки

  • Управлять перетаскиванием из таблицы в ракурс tableViewпеременная в ViewControllerкод.
  • Сделайте то же самое для ярлыка в своей ячейке Prototype. myCellLabelпеременная в MyCustomCellкласс.

Законченный

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

Заметки

  • Этот пример работает только для iOS 8 и после. Если вам все еще нужно поддерживать iOS 7, то это не сработает для вас.
  • У ваших собственных пользовательских ячеек в ваших будущих проектах, вероятно, будет больше одной метки. Убедитесь, что вы все закреплены правильно, чтобы автомат мог определить правильную высоту для использования. Вы также можете использовать вертикальное сопротивление сжатию и обнимать. Видеть Эта статья для получения дополнительной информации об этом.
  • Если вы не закрепляете передний и задний (левый и правый) края, вам также может потребоваться установить метку preferredMaxLayoutWidthтак что он знает, когда переносить строки. Например, если вы добавили ограничение по горизонтали к метке в проекте выше, а не привязывали передний и задний фронты, тогда вам нужно будет добавить эту строку в tableView:cellForRowAtIndexPathметод:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Смотрите также


69



Я упаковал решение 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, но там он пока!


59



Here is my solution. You need to tell the TableView the estimatedHeight before it loads the view. Otherwise it wont be able to behave like expected.

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

43



The solution proposed by @smileyborg is almost perfect. If you have a custom cell and you want one or more UILabel with dynamic heights then the systemLayoutSizeFittingSize method combined with AutoLayout enabled returns a CGSizeZero unless you move all your cell constraints from the cell to its contentView (as suggested by @TomSwift here How to resize superview to fit all subviews with autolayout?).

To do so you need to insert the following code in your custom UITableViewCell implementation (thanks to @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];
    }
}

Mixing @smileyborg answer with this should works.


41



An important enough gotcha I just ran into to post as an answer.

@smileyborg's answer is mostly correct. However, if you have any code in the layoutSubviews method of your custom cell class, for instance setting the preferredMaxLayoutWidth, then it won't be run with this code:

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

It confounded me for awhile. Then I realized it's because those are only triggering layoutSubviews on the contentView, not the cell itself.

My working code looks like this:

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

Note that if you are creating a new cell, I'm pretty sure you don't need to call setNeedsLayout as it should already be set. In cases where you save a reference to a cell, you should probably call it. Either way it shouldn't hurt anything.

Another tip if you are using cell subclasses where you are setting things like preferredMaxLayoutWidth. As @smileyborg mentions, "your table view cell hasn't yet had its width fixed to the table view's width". This is true, and trouble if you are doing your work in your subclass and not in the view controller. However you can simply set the cell frame at this point using the table width:

For instance in the calculation for height:

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);

(I happen to cache this particular cell for re-use, but that's irrelevant).


22



In case people are still having trouble with this. I wrote a quick blog post about using Autolayout with UITableViews Leveraging Autolayout For Dynamic Cell Heights as well as an open source component to help make this more abstract and easier to implement. https://github.com/Raizlabs/RZCellSizeManager


19



(for Xcode 8.x / Xcode 9.x read at the bottom)

Beware of the following issue in in Xcode 7.x, which might be a source of confusion:

Interface Builder does not handle auto-sizing cell set-up properly. Even if your constraints are absolutely valid, IB will still complain and give you confusing suggestions and errors. The reason is that IB is unwilling to change the row's height as your constraints dictate (so that the cell fits around your content). Instead, it keeps the row's height fixed and starts suggesting you change your constraints, which you should ignore.

For example, imagine you've set up everything fine, no warnings, no errors, all works.

enter image description here

Now if you change the font size (in this example I'm changing the description label font size from 17.0 to 18.0).

enter image description here

Because the font size increased, the label now wants to occupy 3 rows (before that it was occupying 2 rows).

If Interface Builder worked as expected, it would resize the cell's height to accommodate the new label height. However what actually happens is that IB displays the red auto-layout error icon and suggest that you modify hugging/compression priorities.

enter image description here

You should ignore these warnings. What you can* do instead is to manually change the row's height in (select Cell > Size Inspector > Row Height).

enter image description here

I was changing this height one click at a time (using the up/down stepper) until the red arrow errors disappear! (you will actually get yellow warnings, at which point just go ahead and do 'update frames', it should all work).

* Note that you don't actually have to resolve these red errors or yellow warnings in Interface Builder - at runtime, everything will work correctly (even if IB shows errors/warnings). Just make sure that at runtime in the console log you're not getting any AutoLayout errors.

In fact trying to always update row height in IB is super annoying and sometimes close to impossible (because of fractional values).

To prevent the annoying IB warnings/errors, you can select the views involved and in Size Inspector for the property Ambiguity choose Verify Position Only



enter image description here


Xcode 8.x / Xcode 9.x seems to (sometimes) be doing things differently than Xcode 7.x, but still incorrectly. For example even when compression resistance priority / hugging priority are set to required (1000), Interface Builder might stretch or clip a label to fit the cell (instead of resizing cell height to fit around the label). And in such a case it might not even show any AutoLayout warnings or errors. Or sometimes it does exactly what Xcode 7.x did, described above.


18



As long as your layout in your cell is good.

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

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

Update: You should use dynamic resizing introduced in iOS 8.


16



Like @Bob-Spryn I ran into an important enough gotcha that I'm posting this as an answer.

I struggled with @smileyborg's answer for a while. The gotcha that I ran into is if you've defined your prototype cell in IB with additional elements (UILabels, UIButtons, etc.) in IB when you instantiate the cell with [[YourTableViewCellClass alloc] init] it will not instantiate all the other elements within that cell unless you've written code to do that. (I had a similar experience with initWithStyle.)

To have the storyboard instantiate all the additional elements obtain your cell with [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (Not [tableView dequeueReusableCellWithIdentifier:forIndexPath:] as this'll cause interesting problems.) When you do this all the elements you defined in IB will be instantiated.


14



Dynamic Table View Cell Height and Auto Layout

A good way to solve the problem with storyboard Auto Layout:

- (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;
}

13