Использование Auto Layout в UITableView для динамического размещения ячеек и переменной высоты строк
Как вы используете Auto Layout в UITableViewCell
Находятся ли в табличном представлении содержимое каждой ячейки и подпредставления определяют высоту строки (само по себе / автоматически), сохраняя при этом плавную прокрутку?
31 ответ
TL;DR: не любишь читать? Перейдите прямо к образцам проектов на GitHub:
- Образец проекта iOS 8 - Требуется iOS 8
- Образец проекта iOS 7 - Работает на iOS 7+
Концептуальное описание
Первые 2 шага ниже применимы независимо от того, для каких версий iOS вы разрабатываете.
1. Установите и добавьте ограничения
В вашем UITableViewCell
подкласс, добавьте ограничения, чтобы у подвидов ячейки были прикреплены края к краям содержимого содержимого ячейки (наиболее важно к верхнему и нижнему краям). ПРИМЕЧАНИЕ: не прикрепляйте подпредставления к самой ячейке; только к клетке contentView
! Позвольте внутреннему размеру контента этих подпредставлений управлять высотой представления контента ячейки табличного представления, следя за тем, чтобы сопротивление сжатию контента и ограничения объятия контента в вертикальном измерении для каждого подпредставления не переопределялись добавленными вами ограничениями более высокого приоритета. ( Да? Нажмите здесь.)
Помните, что идея состоит в том, чтобы подключить подпредставления ячейки вертикально к представлению содержимого ячейки, чтобы они могли "оказывать давление" и расширять представление содержимого в соответствии с ними. Используя пример ячейки с несколькими подпредставлениями, вот наглядная иллюстрация того, как должны выглядеть некоторые (не все!) Ваши ограничения:
Вы можете себе представить, что по мере того, как в метку многострочного тела в приведенной выше ячейке выше добавляется больше текста, он должен будет расти вертикально, чтобы соответствовать тексту, что будет эффективно увеличивать высоту ячейки. (Конечно, для правильной работы необходимо правильно настроить ограничения!)
Правильное определение ограничений - определенно самая трудная и важная часть получения динамических высот ячеек при работе с 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: Работа с ячейками представления таблицы с самоопределением размеров
В 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 & Get The Cell Height
Во-первых, создайте экземпляр закадрового экземпляра ячейки табличного представления, по одному экземпляру для каждого идентификатора повторного использования, который используется строго для вычислений высоты. (Вне экрана означает, что ссылка на ячейку хранится в свойстве / ivar на контроллере представления и никогда не возвращается из tableView:cellForRowAtIndexPath:
чтобы табличное представление действительно отображалось на экране.) Затем ячейка должна быть настроена с точным содержимым (например, текстом, изображениями и т. д.), которое она будет содержать, если она будет отображаться в табличном представлении.
Затем заставьте ячейку немедленно расположить свои подпредставления, а затем используйте systemLayoutSizeFittingSize:
метод на UITableViewCell
"s contentView
выяснить, какова требуемая высота ячейки. использование UILayoutFittingCompressedSize
чтобы получить наименьший размер, необходимый для размещения всего содержимого ячейки. Затем высоту можно вернуть из tableView:heightForRowAtIndexPath:
метод делегата.
4. Используйте Расчетные высоты строк
Если в вашем табличном представлении содержится более двух десятков строк, вы обнаружите, что выполнение решения ограничений Auto Layout может быстро увязнуть с основным потоком при первой загрузке табличного представления, так как 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;
}
}
Примеры проектов
- Образец проекта iOS 8 - Требуется iOS 8
- Образец проекта iOS 7 - Работает на iOS 7+
Эти проекты являются полностью рабочими примерами табличных представлений с переменной высотой строк из-за ячеек табличного представления, содержащих динамическое содержимое в UILabels.
Ксамарин (C#/.NET)
Если вы используете Xamarin, посмотрите этот пример проекта, составленный Kent Boogaart.
Для 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.2UITableViewAutomaticDimension
изменился наUITableView.automaticDimension
Быстрый пример переменной высоты 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
Смотрите также
Я поместил решение @smileyborg для iOS7 в категорию
Я решил обернуть это умное решение @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 stackru.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, но пока он есть!
Вот мое решение. Вам нужно сообщить 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
}
Решение, предложенное @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 с этим должно работать.
Достаточно важный вопрос, с которым я столкнулся, чтобы опубликовать ответ.
Ответ @smileyborg в основном правильный. Однако, если у вас есть какой-либо код в layoutSubviews
метод вашего пользовательского класса ячеек, например, установка preferredMaxLayoutWidth
тогда он не будет запущен с этим кодом:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
Это смутило меня на некоторое время. Тогда я понял, что это потому, что они только вызывают layoutSubviews на contentView
, а не сама клетка.
Мой рабочий код выглядит так:
TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
Обратите внимание, что если вы создаете новую ячейку, я уверен, что вам не нужно звонить setNeedsLayout
как это уже должно быть установлено. В случаях, когда вы сохраняете ссылку на ячейку, вам, вероятно, следует вызвать ее. В любом случае это ничего не должно повредить.
Еще один совет, если вы используете подклассы ячеек, где вы устанавливаете такие вещи, как preferredMaxLayoutWidth
, Как отмечает @smileyborg, "ваша ячейка табличного представления еще не имеет своей ширины, привязанной к ширине табличного представления". Это верно, и проблема, если вы выполняете свою работу в своем подклассе, а не в контроллере представления. Однако вы можете просто установить рамку ячейки в этой точке, используя ширину таблицы:
Например, в расчете на высоту:
self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);
(Мне случается кэшировать эту конкретную ячейку для повторного использования, но это не имеет значения).
В случае, если у людей все еще есть проблемы с этим. Я написал краткое сообщение в блоге об использовании Autolayout с UITableViews, использующим Autolayout для динамических высот ячеек, а также компонент с открытым исходным кодом, чтобы помочь сделать это более абстрактным и более простым для реализации. https://github.com/Raizlabs/RZCellSizeManager
Пока ваш макет в вашей камере хорошо.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}
Обновление: вы должны использовать динамическое изменение размеров, представленное в iOS 8.
(для 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), Интерфейсный Разработчик может растянуть или обрезать метку, чтобы она соответствовала ячейке (вместо изменения высоты ячейки, чтобы она соответствовала метке). И в таком случае он может даже не показывать никаких предупреждений или ошибок AutoLayout. Или иногда он делает именно то, что сделал Xcode 7.x, описанный выше.
Чтобы установить автоматическое измерение для высоты строки и предполагаемой высоты строки, выполните следующие действия, чтобы автоматическое измерение было эффективным для макета высоты ячейки / строки.
- Назначить и реализовать источник данных tableview и делегировать
- приписывать
UITableViewAutomaticDimension
в rowHeight и по оценкам RowHeight - Реализуйте методы делегата / источника данных (т.е.
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) с динамической длиной, которую следует отрегулировать в соответствии с размером ее содержимого: настройте "Приоритет содержания и сопротивления сжатию" для меток, которые вы хотите расширить / сжать с более высоким приоритетом.
Как и Bob Spryn, я столкнулся с достаточно важной Bob Spryn я Bob Spryn в качестве ответа.
Некоторое время я боролся с ответом @smileyborg. Я понял, что вы определили свою ячейку прототипа в IB с дополнительными элементами (UILabels
, UIButtons
и т. д.) в IB, когда вы создаете экземпляр ячейки с помощью [ [YourTableViewCellClass alloc] init]
он не будет создавать все остальные элементы в этой ячейке, если вы не написали код для этого. (У меня был похожий опыт с initWithStyle
.)
Чтобы раскадровка создала все дополнительные элементы, создайте ячейку с [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"]
(Не [tableView dequeueReusableCellWithIdentifier:forIndexPath:]
так как это вызовет интересные проблемы.) Когда вы сделаете это, все элементы, которые вы определили в IB, будут созданы.
Высота ячейки динамического представления таблицы и автоматическое расположение
Хороший способ решить проблему с раскадровкой Auto Layout:
- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
static RWImageCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
});
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
Другое "решение": пропустите все это разочарование и используйте вместо этого UIScrollView, чтобы получить результат, который выглядит и чувствует себя идентичным UITableView.
Это было болезненное "решение" для меня, после того, как я потратил буквально 20+ очень разочаровывающих часов, пытаясь создать что-то вроде того, что предложил смайлиборг, и потерпел неудачу в течение многих месяцев и трех версий выпусков 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 с лишним часов, пытаясь выяснить это с помощью Table View в своем собственном приложении.:)
Мне пришлось использовать динамические представления (настройки представления и ограничения по коду), и когда я захотел установить ширину метки предпочитаемый MaxLayoutWidth, была равна 0. Поэтому у меня неправильная высота ячейки.
Потом я добавил
[cell layoutSubviews];
перед выполнением
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
После этого ширина метки была ожидаемой, а динамическая высота вычислялась правильно.
Допустим, у вас есть ячейка с подпредставлением, и вы хотите, чтобы высота ячейки была достаточно высокой, чтобы охватить подпредставление + заполнение.
1) Установите нижнее ограничение подпредставления равным cell.contentView минус требуемый отступ. Не устанавливайте ограничения для ячейки или самого cell.contentView.
2) Установите либо tableView's rowHeight
собственность или tableView:heightForRowAtIndexPath:
в UITableViewAutomaticDimension
,
3) Установите либо tableView's estimatedRowHeight
собственность или tableView:estimatedHeightForRowAtIndexPath:
чтобы догадаться о высоте.
Вот и все.
Если вы делаете макет программно, вот что нужно учитывать для 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
Если у вас есть длинная строка. например, тот, у которого нет разрыва строки. Тогда вы можете столкнуться с некоторыми проблемами.
Исправление упоминается в принятом ответе и нескольких других ответах. Вам просто нужно добавить
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 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
?
- Длина текста: если у вас более длинный текст, ширина intrinsicContentSize увеличится
- Разрывы строк: если вы добавите
\n
тогда ширина intrinsicContentSize будет максимальной шириной всех строк. Если в одной строке 25 символов, в другой 2 символа, а в другой 21 символ, то ваша ширина будет рассчитана на основе 25 символов. - Количество разрешенных строк: Вы должны установить
numberOfLines
в0
в противном случае у вас не будет нескольких строк. ВашnumberOfLines
отрегулирует высоту вашего intrinsicContentSize Внесение корректировок. Представьте, что на основе вашего текста ширина вашего intrinsicContentSize была
200
и высота была100
, но вы хотели ограничить ширину контейнера этикетки, что вы собираетесь делать? Решение состоит в том, чтобы установить желаемую ширину. Вы делаете это, устанавливаяpreferredMaxLayoutWidth
в130
тогда ваш новый intrinsicContentSize будет иметь ширину примерно130
, Высота, очевидно, будет больше, чем100
потому что вам нужно больше строк. Тем не менее, если ваши ограничения установлены правильно, вам не нужно будет использовать это вообще! Подробнее об этом смотрите этот ответ и его комментарии. Вам нужно только использоватьpreferredMaxLayoutWidth
если у вас нет ограничений, ограничивающих ширину / высоту, как можно было бы сказать "не переносите текст, если он не превышаетpreferredMaxLayoutWidth
Msgstr "Но со 100% уверенностью, если вы установите ведущий / трейлинг иnumberOfLines
в0
тогда ты в порядке! Короче говоря, большинство ответов здесь, которые рекомендуют использовать это НЕПРАВИЛЬНО! Тебе это не нужно. Необходимость в этом является признаком того, что ваши ограничения установлены неправильно или у вас просто нет ограниченийРазмер шрифта: также обратите внимание, что если вы увеличите свой font Size, то высота intrinsicContentSize увеличится. Я не показал это в моем коде. Вы можете попробовать это самостоятельно.
Итак, вернемся к вашему примеру tableViewCell:
Все, что вам нужно сделать, это установить numberOfLines
в 0
и установить preferredMaxLayoutWidth
на ваш tableView
"s width
,
Я просто сделал какую-то глупую попытку и ошибку с 2 значениями 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
Что касается принятого ответа @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: ширина таблицы
В моем случае я должен создать пользовательскую ячейку с изображением, которое приходит с сервера и может быть любой ширины и высоты. И две UILabels с динамическим размером (ширина и высота)
я достиг того же самого в моем ответе с autolayout и программно:
В основном выше @smileyBorg помог ответ, но systemLayoutSizeFittingSize у меня никогда не работал, в моем подходе:
1. Не использовать свойство автоматического расчета высоты строки. 2.Не использовать расчетную высоту 3.Не нужно ненужных обновлений ограничений. 4.Не использовать автоматическую максимальную ширину макета. 5. Не использовать systemLayoutSizeFittingSize (должен был использовать, но у меня не работает, я не знаю, что он делает внутри), но вместо этого мой метод -(float)getViewHeight работает, и я знаю, что он делает внутри.
В моем случае заполнение происходило из-за высоты sectionHeader и sectionFooter, где раскадровка позволила мне изменить его на минимум 1. Так что в методе viewDidLoad:
tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
SWIFT 4.2
Это может помочь вам сократить написание одного и того же кода в приложении в несколько раз.
Я добавил следующее расширение для UITableView:
extension UITableView {
func setDynamicRowHeight(withEstimatedHeight height : CGFloat){
self.estimatedRowHeight = height
self.rowHeight = UITableViewAutomaticDimension
}
}
и в любом классе мне нужно установить высоту строки tableView для динамического
Я только вызываю вышеупомянутую функцию в обратном вызове ViewDidLoad() так просто:
override func viewDidLoad() {
super.viewDidLoad()
// enter your estimated row height.
self.tableView.setDynamicRowHeight(withEstimatedHeight: 88.0)
}
Просто добавьте эти две функции в ваш 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
}
UITableView.automaticDimension
можно установить через Interface Builder:
Xcode> Раскадровка> Инспектор размеров
Ячейка представления таблицы> Высота строки> Автоматически
(1 комплект
tableView.rowHeight = UITableView.automaticDimension
обычно на ваш взглядDidLoad
(2) Просто не включайте
(3) В ячейке на раскадровке. Просто убедитесь, что верхняя часть contentView соединена с помощью ограничений с нижней частью представления контента.
Достичь «3» просто
каждый элемент сверху вниз в ячейке должен быть связан с элементом, находящимся под ним.
верхний элемент связан с верхним представлением содержимого. нижний элемент связан с нижней частью представления содержимого.
каждый элемент должен иметь либо фиксированный размер (например, вы устанавливаете ограничение высоты на «12»), либо это должен быть UILabel (или, в редких случаях, другой элемент, который определяет для вас свою собственную высоту, например стек). вид).
Вот и все.
Если высота ячейки зависит от содержимого, вы должны точно рассчитать ее, а затем вернуть значение высоты до того, как ячейка будет визуализирована. Самый простой способ - определить метод подсчета в коде ячейки табличного представления, который контроллер будет вызывать в методе делегата высоты ячейки таблицы. Не забывайте подсчитывать реальную ширину рамки ячейки (по умолчанию 320), если высота зависит от ширины таблицы или экрана. То есть в методе делегата высоты ячейки таблицы используйте cell.frame, чтобы сначала исправить ширину ячейки, затем вызовите метод подсчета высоты, определенный в ячейке, чтобы получить подходящее значение и вернуть его.
PS. Код для создания объекта ячейки может быть определен в другом методе для вызова другого метода делегата ячейки представления таблицы.
Как уже упоминалось:
override func viewWillAppear(_ animated: Bool) {
self.tableview.rowHeight = UITableView.automaticDimension
self.tableview.estimatedRowHeight = 250
}
24.06.2021 - Советы и хитрости: приведенный выше код работает, но есть некоторые проблемы:
При новом запуске высота первых ячеек рассчитывается не так, как ожидалось.но после прокрутки вниз и затем вверх он рассчитался правильно, поэтому проблема с новым запуском ... Эта проблема возникает, если есть несколько меток и если их номер строки не = 1, автоматический размер вычисляет высоту ячейки неправильно при новом запуске
Свифт 4
@IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var tableView: UITableView!
переопределить func viewDidLoad () {super.viewDidLoad ()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
}
// Добавлен наблюдатель для настройки высоты просмотра таблицы в зависимости от содержимого
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
}
}
}
// Удалить наблюдателя deinit {
NotificationCenter.default.removeObserver(self)
}