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

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

25 ответов


TL; DR: не любите читать? Перейти прямо к образцам проектов на GitHub:

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

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

1. Настройка & Добавить Ограничения

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

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

Example illustration of constraints on a table view cell.

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

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

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

2. Определите Уникальные Идентификаторы Повторного Использования Ячеек Табличного Представления

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

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

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

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

для iOS 8-самостоятельная калибровка ячеек

3. Включить Оценку Высоты Строки

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

Apple: работа с ячейками представления таблицы Self-Sizing

С 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.


для 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, ключ высчитывает высоту после autolayout,

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

важно

  • если несколько строк метки, не забудьте установить numberOfLines to 0.

  • не забудьте label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

полный пример кода здесь.


Swift пример переменной высоты 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
    

настройки раскадровку

добавьте представление таблицы в контроллер вида и используйте auto layout, чтобы прикрепить его к четырем сторонам. Затем перетащите Ячейка представления таблицы в представлении таблицы. И на ячейку прототипа перетащите метку. Используйте auto layout, чтобы прикрепить метку к четырем краям представления содержимого ячейки табличного представления.

enter image description here

важное замечание:

  • Auto layout работает вместе с важными двумя строками кода, о которых я упоминал выше. Если вы не используете auto layout, он не будет работать.

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

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

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

enter image description here

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

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

enter image description here

подключить розетки

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

закончил

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

Примечания

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

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

см. также


я завернул решение iOS7 @smileyborg в категорию

я решил обернуть это умное решение @smileyborg в .

категория также исправляет проблемы, описанные в ответе @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.м

#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, но пока он есть!


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

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

решение, предложенное @smileyborg, почти идеально. Если у вас есть пользовательская ячейка, и вы хотите один или несколько UILabel с динамическими высотами тогда systemLayoutSizeFittingSize метод в сочетании с Autolayout включен возвращает CGSizeZero Если вы не переместите все ограничения ячейки из ячейки в ее contentView (как предложено @TomSwift здесь как изменить размер супервизора, чтобы соответствовать всем подвидам с autolayout?).

для этого вам нужно вставить следующий код в вашей пользовательской реализации 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];
    }
}

Mixing @smileyborg ответ с этим должен работать.


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

ответ@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);

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


в случае, если у людей все еще есть проблемы с этим. Я написал быстрый пост в блоге об использовании Autolayout с UITableViews Использование Автозапуска Для Динамических Высот Ячеек а также компонент с открытым исходным кодом, который поможет сделать это более абстрактным и простым в реализации. https://github.com/Raizlabs/RZCellSizeManager


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

остерегайтесь следующей проблемы в Xcode 7.х, что может быть источником путаницы:

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

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

enter image description here

теперь, если вы измените размер шрифта (в этом примере я изменяю размер шрифта метки описания с 17.0 на 18.0).

enter image description here

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

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

enter image description here

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

enter image description here

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

* обратите внимание, что вам фактически не нужно разрешать эти красные ошибки или желтые предупреждения в Interface Builder - во время выполнения все будет работать правильно (даже если IB показывает ошибки/предупреждения). Просто убедитесь, что во время выполнения в журнале консоли вы не получаете ошибок автозапуска.

На самом деле попытка всегда обновлять высоту строки В IB очень раздражает и иногда близка к невозможности (из-за дробных значений).

Предотвратить раздражающие предупреждения/ошибки IB, вы можете выбрать соответствующие представления и в Size Inspector свойства Ambiguity выбрать Verify Position Only

enter image description here


Xcode 8.x / Xcode 9.кажется, что x (иногда) делает вещи иначе, чем Xcode 7.x, но все же неправильно. Например, даже когда compression resistance priority / hugging priority установлены в required (1000), Interface Builder может растянуть или обрезать метку, чтобы соответствовать ячейке (вместо изменения размера высоты ячейки, чтобы соответствовать вокруг метки). И в таком случае он может даже не показывать никаких предупреждений или ошибок автозапуска. Или иногда он делает именно то, что Xcode 7.x сделал, как описано выше.


пока ваш макет в камере-это хорошо.

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

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

Update: вы должны использовать динамическое изменение размера, введенное в iOS 8.


как @Bob-Spryn я столкнулся с достаточно важным gotcha, что я публикую это в качестве ответа.

боролся с @smileyborg это ответ на некоторое время. Gotcha, с которым я столкнулся, - это если вы определили свою ячейку прототипа В IB с дополнительными элементами (UILabels, UIButtons, etc.) В IB, когда вы создаете экземпляр ячейки с помощью [[YourTableViewCellClass alloc] init] он не будет создавать экземпляры всех других элементов в этой ячейке, если вы не написали код для этого. (У меня было подобное опыт работы с initWithStyle.)

чтобы иметь раскадровку инстанцировать все дополнительные элементы, получите свою ячейку с [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (не [tableView dequeueReusableCellWithIdentifier:forIndexPath:] как это вызовет проблемы.) Когда вы это сделаете, все элементы, определенные В IB, будут созданы.


динамический вид таблицы Высота ячейки и автоматическая компоновка

хороший способ решить проблему с раскадровкой 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;
}

для того чтобы установить автоматический размер для высоты строки & расчетной высоты строки, обеспечьте следующие шаги для того чтобы сделать, автоматический размер эффективный для плана высоты клетки/строки.

  • назначить и реализовать источник данных tableview и делегировать
  • присвоить UITableViewAutomaticDimension к rowHeight & estimatedRowHeight
  • реализовать методы делегата/источник данных (т. е. 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 (&режим разрыва строки = усеченный хвост)
  • установите все ограничения (верхний, нижний, правый левый) относительно своего контейнера superview/ cell.
  • дополнительно: установите минимальную высоту для ярлыка, если вы хотите минимальную вертикальную область покрытую ярлыком, даже если нет данные.

enter image description here

Примечание: если у вас есть несколько меток (UIElements) с динамической длиной, которая должна быть скорректирована в соответствии с ее размером содержимого: отрегулируйте "приоритет обнимания содержимого и сопротивления сжатию" для меток, которые вы хотите развернуть/сжать с более высоким приоритетом.


другое "решение": пропустите все это разочарование и используйте UIScrollView вместо этого, чтобы получить результат, который выглядит и чувствует себя идентичным UITableView.

это было болезненное "решение" для меня, после того, как я поставил буквально 20+ очень разочаровывающие часы, пытаясь построить что-то вроде того, что предложил smileyborg, и потерпел неудачу в течение многих месяцев и трех версий выпусков App Store.

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

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

вот несколько советов о том, как это сделать:

1) используя раскадровку или файл nib, создайте ViewController и связанный корневой вид.

2) перетащите UIScrollView в корневое представление.

3) Добавьте ограничения сверху, снизу, слева и справа в представление верхнего уровня, чтобы UIScrollView заполнял весь корневой вид.

4) Добавьте UIView внутри UIScrollView и назовите его "контейнером". Добавить топ, нижние, левые и правые ограничения для UIScrollView (его родителя). Ключевой трюк: также добавьте ограничения "равной ширины", чтобы связать UIScrollView и UIView.

вы получите сообщение об ошибке "вид прокрутки имеет неоднозначную высоту прокручиваемого содержимого" и что ваш контейнер UIView должен иметь высоту 0 пикселей. Ни одна ошибка не имеет значения, когда приложение работает.

5) Создайте файлы и контроллеры nib для каждой из ваших "ячеек". Используйте UIView не UITableViewCell.

5) В вашем корневом 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!)
    }
}

и вот код для Уитулы.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% существенной, и никто не заметил, когда она ушла.

Если вы хотите, чтобы разделители между вашими ячейками, просто добавьте 1 пиксельный высокий UIView в нижней части вашего пользовательского "ячейка", похожая на разделитель.

обязательно включите "отскоки" и "отскок по вертикали", чтобы элемент управления refresh работал, и поэтому он больше похож на tableview.

TableView показывает некоторые пустые строки и разделители под вашим контентом, если он не заполняет весь экран, где это решение не делает. Но лично я предпочитаю, чтобы эти пустые строки все равно не были-с переменной высотой ячейки мне всегда казалось "багги", чтобы иметь пустые строки в там.

вот надеюсь, что какой-то другой программист читает мой пост, прежде чем тратить 20+ часов, пытаясь понять это с помощью таблицы в своем собственном приложении. :)


tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

enter image description here


мне пришлось использовать динамические представления (представления установки и ограничения по коду), и когда я хотел установить preferredMaxLayoutWidth ширина метки была 0. Значит, я ошибся высотой камеры.

затем я добавил

[cell layoutSubviews];

до исполнения

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

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


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

1) Установите нижнее ограничение subview равным ячейке.contentView минус дополнение, которое вы хотите. Не устанавливайте ограничения для ячейки или ячейки.странице просмотр содержимого себе.

2) Установите либо tableView rowHeight собственность или tableView:heightForRowAtIndexPath: to UITableViewAutomaticDimension.

3) Установите либо tableView estimatedRowHeight собственность или tableView:estimatedHeightForRowAtIndexPath: к лучшему предположению высоты.

вот именно.


Если вы делаете макет программно, вот что нужно учитывать для iOS 10 с помощью якорей в Swift.

есть три правила/ действия

номер 1: установите эти два свойства tableview на viewDidLoad, первый говорит tableview, который должен ожидать динамических размеров на своих ячейках, второй-просто позволить приложению вычислить размер индикатора полосы прокрутки, поэтому это помогает для производительности.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

номер 2: это важно необходимо добавить подвиды в contentView ячейки не в представление, а также использовать его layoutsmarginguide для привязки подвидов сверху и снизу, это рабочий пример того, как это сделать.

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 правилам для динамических клетки в tableviews.

вот рабочая реализация https://github.com/jamesrochabrun/MinimalViewController


в моем случае я должен создать пользовательскую ячейку с изображением, которое поступает с сервера и может быть любой ширины и высоты. И два UILabels с динамическим размером(ширина и высота)

Я достиг того же самого здесь в своем ответе с autolayout и программно:

в основном выше @smileyBorg ответ помог, но systemLayoutSizeFittingSize никогда не работал для меня, в моем подходе:

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

возможно ли иметь разные высоты в ячейке UITableView, когда я использую несколько разных способов отображения сотовый?


в моем случае заполнение было из-за высоты sectionHeader и sectionFooter, где раскадровка позволила мне изменить его на минимум 1. Итак, в методе viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

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

, если вы установите их оба или только estimatedRowHeight вы получите желаемое поведение:

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

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

enter image description here


если вы только установите rowHeight ie только:

tableView.rowHeight = UITableViewAutomaticDimension

ваш конечный результат не будет желаемым:

enter image description here


если вы выберите estimatedRowHeight к 1 или меньше, то вы будете авария независимо от 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

что касается принятого ответа @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: - ширина tableview


если у вас длинная строка. например, тот, у которого нет разрыва линии. Тогда у тебя могут возникнуть проблемы.

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

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

найти Suragh это наиболее полный, будучи наименее запутанным.

хотя не объяснить почему. Давайте сделаем это.

поместите следующий код в проект.

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 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.

чтобы лучше обработать это, сначала сохраните Шаг 0, а затем прокомментируйте шаги 1-6. Пусть setupLayout() остановиться. Наблюдайте за поведением.

затем раскомментируйте Шаг 1 и наблюдайте.

затем раскомментируйте Шаг 2 и наблюдайте.

делайте это, пока не раскомментировал все 6 шагов и наблюдал за их поведением.

что можно заключить из всего этого? Какие факторы могут изменить contenSize?

  1. Текст Длина: если у вас есть более длинный текст, то ваш intrinsicContentSize ширина увеличится
  2. разрывы строк: если добавить \n тогда ширина intrinsicContentSize будет максимальной шириной всех линий. Если одна строка содержит 25 символов, другая-2 символы и другой имеет 21 символов, то ваша ширина будет рассчитываться на основе 25 символов
  3. количество разрешенных строк: необходимо указать numberOfLines to 0 в противном случае у вас не будет нескольких строк. Ваш numberOfLines отрегулирует ваш intrinsicContentSize в высота
  4. внесение корректировок: представьте, что на основе вашего текста ширина вашего intrinsicContentSize была 200 и высоты 100, но вы хотели ограничить ширину контейнера этикетки, что вы собираетесь делать? Решение установить его на нужную ширину. Вы делаете это, установив preferredMaxLayoutWidth до 130 тогда ваш новый intrinsicContentSize будет иметь ширину примерно 130. Высота, очевидно, будет больше, чем 100 потому что вам нужно больше строк. Это, как говорится, если ваши ограничения установлены правильно, то вам не нужно будет использовать это вообще! Подробнее об этом см. ответ и его комментарии. Вам нужно только использовать preferredMaxLayoutWidth если у вас нет ограничений, ограничивающих ширину / высоту, как в одном, можно сказать: "не обертывайте текст, если он не превышает preferredMaxLayoutWidth". Но со 100% уверенностью, если вы установите начальные/конечные и numberOfLines to 0 тогда ты хороша! короче говоря большинство ответов здесь, которые рекомендуют использовать его неправильно! Тебе это не нужно. Это признак того, что ваши ограничения установлены неправильно или что у вас просто нет ограничения

  5. Размер Шрифта: также обратите внимание, что если вы увеличите размер шрифта, то intrinsicContentSize высота будет увеличиваться. Я не показал этого в своем коде. Можете попробовать сами.

Итак, вернемся к вашему примеру tableViewCell:

все, что вам нужно сделать, это установить numberOfLines to 0 и выберите preferredMaxLayoutWidth на tableView ' s width.


просто добавьте эти две функции в свой viewcontroller, это решит вашу проблему. Здесь list-это строковый массив, который содержит строку каждой строки.

 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
}

еще одно решение 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
    }
}