UITableView гибкая/динамическая heightForRowAtIndexPath

Case

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

К сожалению, метод делегата heightForRowAtIndexPath вызывается перед методом делегата cellForRowAtIndexPath, поэтому мы не можем просто сказать делегату вернуть высоту ячейки, так как в это время это будет 0.

Таким образом, нам нужно вычислить размер до того, как ячейка будет нарисована в таблице. К счастью, существует метод, который делает именно это, sizeWithFont, который принадлежит классу NSString. Однако есть проблема, чтобы динамически вычислять правильный размер, чтобы знать, как будут представлены элементы в ячейке. Я сделаю это в примере:

Представьте себе UITableViewCell, который содержит метку с именем textLabel. В рамках метода делегата cellForRowAtIndexPath мы помещаем textLabel.numberOfLines = 0, который в основном сообщает ярлыку, что он может иметь столько строк, сколько ему нужно представить текст для определенной ширины. Проблема возникает, если мы придаем textLabel текст, который больше ширины, первоначально заданной для textLabel. Появится вторая строка, но высота ячейки не будет автоматически скорректирована, и мы получим беспорядочный вид таблицы.

Как было сказано ранее, мы можем использовать sizeWithFont для вычисления высоты, но он должен знать, какой шрифт используется, для какой ширины и т.д. Если по соображениям простоты мы просто заботимся о ширине, мы могли бы жестко кодировать что ширина будет около 320,0 (при этом не учитывается прокладка). Но что произойдет, если мы будем использовать UITableViewStyleGrouped вместо простой ширины, тогда будет около 300,0, и ячейка снова будет испорчена. Или что происходит, если мы переходим от портрета к пейзажу, у нас гораздо больше места, но он не будет использоваться, так как мы жестко закодировали 300.0.

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

Мои собственные мысли

Вы можете вызвать метод cellForRowAtIndexPath, который принадлежит классу UITableView, чтобы получить ячейку для определенного раздела и строки. Я прочитал пару сообщений, в которых говорилось, что вы не хотите этого делать, но я этого не понимаю. Да, я согласен, что он уже выделит ячейку, но метод делегата heightForRowAtIndexPath вызывается только для ячеек, которые будут видны, поэтому ячейка будет выделена в любом случае. Если вы правильно используете dequeueReusableCellWithIdentifier, ячейка не будет снова выделена в методе cellForRowAtIndexPath, вместо этого используется указатель и свойства будут только что настроены. Тогда какая проблема?

Обратите внимание, что ячейка НЕ ​​рисуется внутри метода делегата cellForRowAtIndexPath, когда ячейка просмотра таблицы становится видимой, script вызывает метод setNeedDisplay в UITableVieCell, который запускает метод drawRect для рисования ячейки, Поэтому вызов делегата cellForRowAtIndexPath напрямую не потеряет производительность, потому что его нужно сделать дважды.

Итак, вызывая метод делегата cellForRowAtIndexPath в методе делегата heightForRowAtIndexPath, мы получаем всю необходимую информацию о ячейке для определения ее размера.

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

Заключение/Вопрос

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

Ответ 1

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

Это неверно. В представлении таблицы необходимо вызвать heightForRowAtIndexPath (если он реализован) для всех строк, которые находятся в представлении таблицы, а не только отображаемых в настоящее время. Причина в том, что ему нужно выяснить его общую высоту, чтобы отобразить правильные индикаторы прокрутки.

Ответ 2

Я использовал это:

  • Создание объектов коллекции (массив информации о размере (словарь, NSNumber высоты строк и т.д.) на основе объектов коллекции, которые будут использоваться для представления таблицы.

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

  • Я предопределяю тип и размер шрифта, который будет использоваться, когда я создаю эти объекты коллекции. Вы даже можете хранить объекты UIFont или любые пользовательские объекты, используемые для представления содержимого.

  • Эти объекты коллекции будут использоваться каждый раз, когда я реализую протоколы UITableViewDataSource или UITableViewDelegate для определения размеров экземпляров UITableViewCell и его подзонов и т.д.

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

Не используйте абсолютное значение для инициализации фреймов. Используйте относительное значение, основанное на текущей ориентации и границах.

Если мы повернем его к любой ориентации, просто создайте механизм изменения размера во время выполнения. Убедитесь, что функция autoresizingMask установлена ​​правильно.

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

Ответ 3

Вот мой подход к решению этого

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

Как обновить высоту:

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

    // We want the UIFont to be the same as what is in the nib,
    //  but we dont want to call tableView dequeue a bunch because its slow. 
    //  If we make the font static and only load it once we can reuse it every
    //  time we get into this method
    static UIFont* dynamicTextFont;
    static CGRect textFrame;
    static CGFloat extraHeight;
    if( !dynamicTextFont ) {
        DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        dynamicTextFont = cell.resizeLabel.font;
        CGRect cellFrame = cell.frame;
        textFrame = cell.resizeLabel.frame;
        extraHeight = cellFrame.size.height-textFrame.size.height; // The space above and below the growing field
    }
    NSString* text = .... // Get this from the some object using indexPath 

    CGSize  size = [text sizeWithFont:dynamicTextFont constrainedToSize:CGSizeMake(textFrame.size.width, 200000.f) lineBreakMode:UILineBreakModeWordWrap];

    return size.height+extraHeight;
}

Вопросы:

  • Если вы не используете ячейку прототипа, вам нужно будет проверить, нет ли ячейки, и ее инициализировать.
  • У вашего nib/раскадровки должна быть автоматическая идентификация UILabel, а для нескольких строк - 0

Ответ 4

Вы должны взглянуть на TTTableItemCell.m в структуре Three20. Это следует из другого подхода, в основном, если каждый класс ячейки (с некоторыми предопределенными настройками, такими как шрифт, макет и т.д.) Реализует общий метод + tableView: sizeForItem: (или что-то в этом роде), где он получает текст в объекте item. Когда вы просматриваете текст для определенной ячейки, вы также можете найти соответствующий шрифт.

Что касается высоты ячейки: вы можете проверить ширину таблицы и, при необходимости, вычесть поля на UITableViewStyleGrouped и ширину конечной строки указателя и элемента раскрытия (который вы ищете в хранилище данных для данных ваших ячеек). Когда изменяется ширина таблицыView, например. по повороту интерфейса вы должны вызвать [tableView reloadData].

Ответ 5

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

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

Ответ 6

У меня есть идея о динамической высоте ячейки.

Просто создайте один экземпляр вашей пользовательской ячейки в качестве переменной-члена UITableViewController. В методе tableView:heightForRowAtIndexPath: задайте содержимое ячейки и верните высоту ячейки.

Таким образом, вы не будете создавать/автореализовывать ячейку несколько раз, как вы, если вы вызовете cellForRowAtIndexPath внутри метода heightForRowAtIndexPath.

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

tableView:heightForRowAtIndexPath: Тело функции теперь будет выглядеть так:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [MyCell cellHeightForContent:yourContent];
}

Ответ 7

Здесь мое решение, которое я использовал для реализации некоторых довольно гладких ячеек для чат-приложения.

До этого момента меня всегда очень раздражало heightForCellAtIndexPath: потому что это приводит к нарушению принципа DRY. С этим решением моя heightForRowAtIndexPath: стоит 1,5 мс на ячейку, которую я мог бы сбрить до ~ 1 мс.

В принципе, вы хотите, чтобы каждый subview внутри вашей ячейки реализовал sizeThatFits: создайте внеэкранную ячейку, которую вы настраиваете, затем запрашиваете корневой вид с помощью sizeThatFits: CGSizeMake (tableViewWidth, CGFLOAT_MAX).

На пути есть несколько ошибок. Некоторые виды UIKit имеют дорогие операции сеттера. Например, [UITextView setText] выполняет большую работу. Трюк здесь заключается в создании подкласса, буферизации переменной, затем переопределении setNeedsDisplay для вызова - [super setText:], когда представление будет отображаться. Разумеется, вам придется реализовать свой собственный размерThatFits: используя расширения UIKit.