Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Example illustration of constraints on a table view cell.

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

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

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

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

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

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

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

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

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

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

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

Apple: Working with Self-Sizing Table View Cells

В 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 Layout & Получить клеточную высоту

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

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

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

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

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

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

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

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

Типовой код iOS 7 (с множеством сочных комментариев)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Determine which reuse identifier should be used for the cell at this 
    // index path, depending on the particular layout required (you may have
    // just one, or may have many).
    NSString *reuseIdentifier = ...;

    // Dequeue a cell for the reuse identifier.
    // Note that this method will init and return a new cell if there isn't
    // one available in the reuse pool, so either way after this line of 
    // code you will have a cell with the correct constraints ready to go.
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];

    // Configure the cell with content for the given indexPath, for example:
    // cell.textLabel.text = someTextForThisCell;
    // ...

    // Make sure the constraints have been set up for this cell, since it 
    // may have just been created from scratch. Use the following lines, 
    // assuming you are setting up constraints from within the cell 
    // 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 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 
    // 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 height depends on its width (due to 
    // multi-line UILabels word wrapping, etc). We don't need to do this 
    // above in -[tableView:cellForRowAtIndexPath] because it happens 
    // automatically when the cell is used in the table view. Also note, 
    // the final width of the cell may not be the width of the table view in
    // some cases, for example when a section index is displayed along 
    // the right side of the table view. You must account for the reduced 
    // cell width.
    cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));

    // Do the layout pass on the cell, which will calculate the frames for 
    // all the views based on the constraints. (Note that you must set the 
    // preferredMaxLayoutWidth on multiline UILabels inside the 
    // -[layoutSubviews] method of the UITableViewCell subclass, or do it 
    // manually at this point before the below 2 lines!)
    [cell setNeedsLayout];
    [cell layoutIfNeeded];

    // Get the actual height required for the cell 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 contentView and the 
    // bottom of the table view cell.
    height += 1.0;

    return height;
}

// NOTE: Set the table view 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 within an order of magnitude of the 
    // actual height. For example:
    if ([self isTallCellAtIndexPath:indexPath]) {
        return 350.0;
    } else {
        return 40.0;
    }
}

Примеры проектов

Эти проекты являются полностью рабочими примерами табличных представлений с переменной высотой строк из-за ячеек табличного представления, содержащих динамическое содержимое в UILabels.

Хамарин (С#/. NET)

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

Ответ 2

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

Ответ 3

Пример 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
    

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

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

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

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

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

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

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

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

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

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

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

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

Подключить выходы

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

Пройденные

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

Примечания

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

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

См. также

Ответ 4

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

Я решил обернуть это умное решение @smileyborg в категорию UICollectionViewCell+AutoLayoutDynamicHeightCalculation.

Категория также устраняет проблемы, описанные в ответе @wildmonkey (загрузка ячейки из nib и systemLayoutSizeFittingSize: return 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, но он есть на данный момент!

Ответ 5

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

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

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
}

Ответ 6

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

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

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

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

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

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

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

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

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

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

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

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

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

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

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

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

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

* Обратите внимание, что на самом деле вам не нужно устранять эти красные ошибки или желтые предупреждения в Interface Builder - во время выполнения все будет работать правильно (даже если 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 сделал, описанное выше.

Ответ 11

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

  • Присвоить и реализовать данные tableviewSource и делегировать
  • Назначить UITableViewAutomaticDimension для rowHeight & оценкамRowHeight
  • Внедрите методы delegate/dataSource (например, heightForRowAt и верните значение UITableViewAutomaticDimension)

-

Цель C:

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

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

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

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

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

    return UITableViewAutomaticDimension;
}

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

enter image description here

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

Ответ 12

Как @Bob-Spryn Я столкнулся с достаточно важным вопросом, который я отправляю в качестве ответа.

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

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

Ответ 13

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

Хороший способ решить проблему с помощью раскладки раскладки:

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

Ответ 14

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

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

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

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

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

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

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

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

4) Добавьте UIView внутри UIScrollView и назовите его "контейнер". Добавьте ограничения сверху, снизу, слева и справа в UIScrollView (его родительский элемент). KEY TRICK: Также добавьте ограничения "Равная ширина", чтобы связать 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!)
    }
}

И вот код для 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 пиксель в нижней части пользовательской ячейки, которая выглядит как разделитель.

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

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

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

Ответ 15

tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

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

Ответ 16

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

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

[cell layoutSubviews];

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

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

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

Ответ 17

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

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

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

3) Задайте свойство tableView estimatedRowHeight или tableView:estimatedHeightForRowAtIndexPath: для наилучшего предположения о высоте.

Что это.

Ответ 18

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

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

НОМЕР 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

Ответ 20

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

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

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

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

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

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

import UIKit

class ViewController: UIViewController {

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

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

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

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

Из-за contentSize.

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

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

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

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

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

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

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

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

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

  • установите для numberOfLines значение 0
  • правильно прикрепить метку к краям/краям
  • Нет необходимости устанавливать preferredMaxLayoutWidth.

Ответ 21

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

Я получил то же самое в моем ответе с автозапуском и программно:

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

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

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

Ответ 22

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

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

Ответ 23

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

Если вы установили их как для OR, а только для установки 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

Ответ 24

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

Ответ 25

Попробовали:

func textViewDidChange(_ textView: UITextView) {
    var frame = textView.frame
    frame.size.height = textView.contentSize.height
    textView.frame = frame
    print(frame)
    self.tableView.rowHeight = frame.size.height

но хотя я получаю правильную высоту кадра, строка не корректирует

Ответ 26

Вот решение, если оно содержит только текст:

private func estimateFrameForText(text: String) -> CGRect {
    let size = CGSize(width: 300.0, height: 1000)
    let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
    return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17.0)], context: nil)
}

Ответ 27

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

 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
}

Ответ 28

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)

    }

Ответ 29

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

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

Ответ 30

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