TL; DR: не любишь читать? Перейдите прямо к образцам проектов на GitHub:
Концептуальное описание
Первые 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.) Общая идея состоит в том, чтобы позволить механизму 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'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 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's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
Примеры проектов
Эти проекты являются полностью рабочими примерами табличных представлений с переменной высотой строк из-за ячеек табличного представления, содержащих динамическое содержимое в UILabels.
Ксамарин (C # /. NET)
Если вы используете Xamarin, посмотрите этот пример проекта, составленный @KentBoogaart .