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

вопрос создан в Wed, Oct 11, 2017 12:00 AM

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

    
1446
  1. Вот пример кода в swift 2.3 github.com/dpakthakur /DynamicCellHeight
    2016-12-17 14: 09: 56Z
  2. Чтобы понять большинство ответов относительно наличия многострочного UILabel, вам необходимо полностью понять, как работают contentSize и preferredMaxLayoutWidth. См. здесь . Это означает, что если вы правильно настроили свои ограничения, то вам не нужен preferredMaxLayoutWidth, и он может привести к неожиданным результатам.
    2018-10-09 02: 41: 58Z
26 ответов                              26                         

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

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

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

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

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

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

Пример иллюстрации ограничений на ячейку табличного представления.

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

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

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

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

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

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

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

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

Для iOS 8 - саморазмерные ячейки

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

  

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

     

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

В 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. Сделать макет Pass & Получить клеточную высоту

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

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

4. Использовать примерную высоту строки

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

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

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

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

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

Типовой код 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 .

    

2340
2018-01-24 05: 24: 23Z
  1. Если бы я был там на 95%, оставшиеся 5% заняли бы у меня пол недели, чтобы понять! Я действительно ценю, что ты так быстро протягиваешь руку. На GitHub работает рабочий код, так что остальная часть сообщества может получить выгоду. Действительно отличная работа smileyborg!
    2013-10-06 02: 18: 06Z
  2. @ Alex311 Очень интересно, тспасибо за предоставление этого примера. Я провёл небольшое тестирование с моей стороны и написал несколько комментариев здесь:
    2013-11-18 21: 31: 17Z
  3. Я бы настоятельно рекомендовал кэшировать ячейку для каждого типа идентификатора повторного использования ячейки. Каждый раз, когда вы уменьшаете высоту, вы берете ячейку из очереди, которая не добавляется обратно. Кэширование может значительно снизить количество вызовов инициализатора для ячеек таблицы. По какой-то причине, когда я не кэшировал, объем используемой памяти продолжал расти, как я прокручивал.
    2013-12-18 22: 19: 24Z
  4. серьезно .... Apple не могла просто разрешить cell.rowHeight = DYNAMIC_HEIGHT; возвратная ячейка; ????? Что они думали ???
    2014-06-13 20: 52: 05Z
  5. @ NathanBuggia, ооо Боже !! Уважаемое яблоко, неужели так сложно просто поставить wrap_content где-нибудь там?
    2016-06-13 12: 06: 24Z

Для 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)

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

РЕДАКТИРОВАТЬ Swift 4.2 UITableViewAutomaticDimension изменен на UITableView.automaticDimension р>     

155
2018-11-07 04: 11: 28Z
  1. Я думаю, нам не нужно реализовывать функцию heightForRowAtIndexPath в случае OS8
    2015-07-16 07: 29: 12Z
  2. Как @eddwinpaz отмечает в другом ответе, важно, чтобы ограничения, используемые для блокировки высоты строки, НЕ включали поле.
    2016-09-11 16: 10: 02Z
  3. + 1 для "Если несколько строк помечены, не забудьте установить для numberOfLines значение 0", это привело к тому, что мои ячейки не были динамическими по размеру.
    2017-05-27 20: 00: 14Z
  4. Назовите меня уродом управления, но даже во времена iOS 11 мне все еще не нравится использовать UITableViewAutomaticDimension. Был некоторый неудачный опыт с этим в прошлом. Поэтому я обычно использую решения iOS7, перечисленные здесь. Примечание Уильяма не забывать, что label.preferredMaxLayoutWidth спасло меня.
    2018-06-22 17: 55: 10Z
  5. @ WilliamHu Спасибо, я решил это. У меня был вид внутри камеры со статической высотой. Сделал это динамическим и удалил sizeToFit () из кода, и это работает.
    2019-06-18 07: 46: 22Z

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

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

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

Готовый проект должен выглядеть следующим образом:

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

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

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

Добавьте код

Добавьте новый файл Swift в свой проект. Назовите это MyCustomCell. Этот класс будет содержать выходы для представлений, которые вы добавляете в свою ячейку в раскадровке. В этом базовом примере у нас будет только одна метка в каждой ячейке.

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

Мы подключим эту розетку позже.

Откройте ViewController.swift и убедитесь, что у вас есть следующее содержимое:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

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

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

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

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

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

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

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

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

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

Важное примечание.

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

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Настройте раскадровку

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

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

Важное примечание.

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

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

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

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

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

Нулевые линии для метки

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

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

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

  • Перетащите элемент управления из табличного представления в раскадровке в переменную tableView в коде ViewController.
  • Сделайте то же самое для метки в ячейке прототипа с переменной myCellLabel в классе MyCustomCell.

Пройденные

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

Примечания

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

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

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

87
2017-05-23 10: 31: 39Z
  1. Не лучше ли на самом деле установить preferredMaxLayoutWidth против contentSize ячейки? Таким образом, если у вас есть accessoryView или он был отсканирован для редактирования, он все равно будет принят во внимание?
    2018-10-05 22: 15: 50Z
  2. @ Дорогая, ты вполне можешь бытьGHT. Я не слежу за iOS уже около года, и я слишком заржавел, чтобы прямо сейчас ответить вам.
    2018-10-06 00: 28: 08Z

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

    
65
2017-03-20 17: 28: 24Z
  1. Вы просто должны иметь возможность просто использовать: [YourCell new] и использовать его в качестве фиктивного. Пока код создания кода ограничения запускается в вашем экземпляре, и вы запускаете передачу макета программным способом, вам будет хорошо.
    2014-07-09 18: 32: 46Z
  2. Спасибо! Это работает. Ваша категория отличная. Именно это заставило меня понять, что эта техника работает и с UICollectionViews.
    2014-07-09 18: 52: 29Z
  3. Как бы вы сделали это, используя ячейку прототипа, определенную в раскадровке?
    2015-01-04 01: 28: 14Z

Вот мое решение:

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

Objective-C сильный> р>

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

Обновление до Swift 4.2

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}
    
52
2019-05-24 14: 57: 42Z
  1. Правильная настройка автоматического размещения, а также добавление этого кода в viewDidLoad сделали свое дело.
    2015-04-19 03: 46: 16Z
  2. но что, если estimatedRowHeight варьируется строка за строкой? я должен преувеличивать или недооценивать? использовать минимальную или максимальную высоту, которую я использую в tableView?
    2015-06-09 12: 38: 51Z
  3. @ Янос, это точка rowHeight. Чтобы сделать это, как ожидается, вам нужно использовать ограничения без полей и выровнять объекты TableViewCell. и я предполагаю, что вы используете UITextView, так что вам все равно нужно удалить autoscroll = false, иначе он будет поддерживать высоту и относительную высоту не будет работать, как ожидалось.
    2015-10-23 05: 16: 36Z
  4. Это, безусловно, самое надежное решение. Не беспокойтесь о estimatedRowHeight, это в основном влияет на размер полосы прокрутки, а не на высоту реальных ячеек. Будьте жирны на выбранной вами высоте: это повлияет на анимацию вставки /удаления.
    2016-01-13 22: 41: 46Z
  5. Вот пример кода в swift 2.3 github.com/dpakthakur /DynamicCellHeight
    2016-12-17 14: 11: 08Z

Решение, предложенное @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];
    }
}

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

    
46
2017-05-23 10: 31: 39Z
  1. systemLayoutSizeFittingSize должен вызываться в contentView, а не в ячейке
    2014-09-25 05: 24: 31Z

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

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

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

    
25
2013-12-03 06: 03: 00Z

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

    
22
2014-03-05 15: 53: 36Z

Пока ваш макет в вашей ячейке хорош.

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

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

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

    
19
2016-05-12 22: 11: 35Z
  1. Это работает для меня на iOS7, можно ли сейчас вызывать tableView:cellForRowAtIndexPath: в tableView:heightForRowAtIndexPath: сейчас?
    2014-06-08 08: 37: 37Z
  2. Хорошо, так что это не работает, но когда я вызываю systemLayoutSizeFittingSize: в tableView:cellForRowAtIndexPath: и кеширую результат, а затем использую его в tableView:heightForRowAtIndexPath:, он работает хорошо, пока установлены ограничения правильно конечно!
    2014-06-08 09: 21: 45Z
  3. В iOS 7 это работает. Как заставить его работать в iOS 8?
    2015-02-07 16: 03: 26Z
  4. Это работает, только если вы используете dequeueReusableCellWithIdentifier: вместо dequeueReusableCellWithIdentifier: forIndexPath:
    2015-02-14 13: 43: 03Z
  5. Я действительно не думаю, что вызов tableView: cellForRowAtIndexPath: напрямую является хорошим способом.
    2016-05-09 03: 36: 11Z

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

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

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

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

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

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

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

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

Если бы Интерфейсный Разработчик работал как ожидалось, он изменил бы высоту ячейки, чтобы приспособить новую высоту метки. Однако в действительности IB отображает красный значок ошибки автоматического макета и предлагает изменить приоритеты сжатия /сжатия.

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

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

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

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

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

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

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

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

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

    
19
2017-07-25 08: 47: 55Z
  1. Ух ты, спасибо !! Я не думал, что Apple будет такой глупой после двух версий ОС с автоматическими ячейками! Но оказывается, нам нужно подождатьIOS15 для этого, чтобы работать правильно. В любом случае, вы избавили меня от проблем, связанных со взломом свойств ячеек, других представлений и т. Д.
    2016-08-27 06: 43: 49Z
  2. Привет, возможно ли дать динамическую высоту для ячейки, имеющей табличное представление с ячейкой с динамическим содержимым ячейки.?
    2017-07-18 18: 46: 14Z

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

  • Назначьте и реализуйте источник данных 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;
}

Swift:

@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.
  • Необязательно . Установите минимальную высоту для метки, если вы хотите, чтобы минимальная вертикальная область была покрыта меткой, даже если данных нет.

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

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

    
18
2018-06-05 14: 23: 22Z
  1. Спасибо, кажется ясным и простым решением, но у меня есть проблема, я не использую метки, но текстовые представления, поэтому мне нужно увеличить высоту строки при добавлении данных. Тогда моя проблема - передать информацию в heightForRowAt. Я могу измерить высоту меняющихся текстовых представлений, но теперь нужно изменить высоту строки. Буду признателен за помощь, пожалуйста,
    2017-10-31 06: 52: 33Z
  2. @ JeremyAndrews Конечно вам поможет. Поднимите свой вопрос с помощью исходного кода, который вы пробовали, и подробно опишите проблему.
    2017-10-31 09: 28: 55Z
  3. спасибо, вы экономите день < 3
    2018-08-16 09: 27: 44Z
  4. Это работает для меня без реализации источника данных tableView: heightForRow.
    2018-10-01 12: 46: 29Z
  5. @ Hemang - решение зависит от точного определения запроса. Здесь у меня есть общее общее решение для вашего запроса. Поместите условие внутрь tableView: heightForRow .. if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
    2018-10-01 12: 57: 57Z

Как и @ Bob-Spryn , я столкнулся с достаточно важной ошибкой, которую я публикую в качестве ответа . р>

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

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

    
16
2017-05-23 12: 18: 33Z

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

Хороший способ решить проблему с раскадровкой 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;
}
    
15
2014-11-05 09: 34: 38Z
  1. Это уже было подробно рассмотрено в принятом ответе на этот вопрос.
    2014-08-22 14: 44: 59Z
  2. Да, я знаю ... но я не хотел использовать PureLayout, а трюк dispatch_once мне очень помог, разобраться с этим только с помощью раскадровки .
    2014-08-22 21: 30: 34Z

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

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

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

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

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

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

2) Перетащите UIScrollView на ваш корневой вид.

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

4) Добавьте UIView внутри UIScrollView и назовите его «контейнер». Добавьте верхний, нижний, левый и правый ограничения в UIScrollView (его родитель). КЛЮЧЕВЫЙ КЛЮЧ: также добавьте ограничения «Равная ширина», чтобы связать UIScrollView и UIView.

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

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

5) В своем корневом ViewController вы по существу добавляете все «строки» к контейнеру UIView и программно добавляете ограничения, связывающие их левый и правый края с видом контейнера, а их верхние края - либо с верхней части вида контейнера (для первого пункт) или предыдущая ячейка. Затем свяжите последнюю ячейку с дном контейнера.

Для нас каждая «строка» находится в файле пера. Так что код выглядит примерно так:

class YourRootViewController {

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

    override func viewDidLoad() {

        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

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

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

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

А вот код для UITools.addViewToTop:

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

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

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

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

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

Единственная «уловка», которую я обнаружил при таком подходе, заключается в том, что UITableView имеет хорошую функцию «плавающих» заголовков разделов в верхней части представления при прокрутке. Вышеупомянутое решение не сделает этого, если вы не добавите больше программирования, но для нашего конкретного случая эта функция не была на 100% существенной, и никто не заметил, когда она ушла.

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

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

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

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

    
13
2015-06-14 22: 17: 20Z
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

enter описание изображения здесь

    
12
2016-10-30 12: 43: 50Z

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

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

[cell layoutSubviews];

перед выполнением

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

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

    
11
2015-09-01 22: 37: 52Z

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

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

2) Установите для свойства rowHeight tableView или для tableView:heightForRowAtIndexPath: значение UITableViewAutomaticDimension.

3) Задайте либо свойство tableView estimatedRowHeight, либо tableView:estimatedHeightForRowAtIndexPath:, чтобы наилучшим образом определить высоту.

Вот и все.

    
8
2016-07-18 17: 37: 44Z

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

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

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

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

НОМЕР 2: Это важно, вам нужно добавить подпредставления в contentView ячейки, а не в представление, а также использовать ее макет макета для привязки подпредставлений к верху и низу, это рабочий пример того, как это сделать. он.

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

private func setUpViews() {

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

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

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

        ])
}

Создайте метод, который добавит подпредставления и выполнит макет, вызовите его в методе init.

НОМЕР 3: НЕ ВЫЗЫВАЙТЕ МЕТОД:

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

Если вы сделаете это, вы переопределите свою реализацию.

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

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

    
4
2017-04-28 15: 59: 47Z
  1. в Swift 5 UITableViewAutomaticDimension переименован в UITableView.automaticDimension
    2019-05-03 19: 05: 40Z

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

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

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Я считаю ответ Суры наиболее полным и лаконичным, а следовательно, не сбивающим с толку.

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

Добавьте следующий код в проект.

import UIKit

class ViewController: UIViewController {

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

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

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

Обратите внимание, что я не добавил никаких ограничений size . Я только добавил centerX, centerY ограничения. Но все равно этикетка будет иметь правильный размерЗачем?

Из-за contentSize.

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

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

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

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

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

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

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

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

Все, что вам нужно сделать, это:

  • установите numberOfLines на 0
  • правильно привязывайте метку к полям /краям
  • Нет необходимости устанавливать preferredMaxLayoutWidth.
3
2019-04-03 22: 07: 55Z

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

Я достиг того же самого в своем ответе с помощью автоматического размещения и программно:

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

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

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

    
1
2017-05-23 10: 31: 39Z

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

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
    
1
2017-01-25 07: 35: 59Z

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

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

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

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

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

Если вы устанавливаете только rowHeight, т.е. только делаете:

tableView.rowHeight = UITableViewAutomaticDimension

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

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

Если вы установите значение 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
    
1
2017-11-25 01: 56: 43Z

Что касается принятого ответа от @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: ширина таблицы

    
1
2017-12-18 22: 31: 26Z

Просто добавьте эти две функции в ваш 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
}
    
0
2018-05-16 06: 08: 31Z
swift 4

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

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

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

//Remove observer
 deinit {

        NotificationCenter.default.removeObserver(self)

    }
    
- 1
2019-03-07 08: 56: 36Z

еще одно решение 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
    }
}
    
- 4
2016-03-14 07: 20: 21Z
  1. note: systemLayoutSizeFittingSize не работает в моем случае
    2016-01-16 15: 14: 25Z
  2. неверная высота ячейки в cellForRowAtIndexPath, ячейка еще не размечена в этой точке.
    2016-01-28 10: 49: 15Z
  3. в iOs7 это фиксированное значение, т.е. оно работает. Вы можете установить снаружи cellForRowAtIndexPath, если хотите
    2016-01-28 16: 40: 01Z
источник размещен Вот
Другие вопросы