UITableView гибкая / динамическая высотаForRowAtIndexPath - PullRequest
60 голосов
/ 06 февраля 2010

Case

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

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

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

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

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

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

Мои мысли

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

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

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

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

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

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

Ответы [ 7 ]

26 голосов
/ 12 июля 2011

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

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

8 голосов
/ 06 февраля 2010

Раньше я делал это по:

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

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

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

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

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

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

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

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

7 голосов
/ 08 августа 2012

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

  1. В этом решении я предполагаю, что только одна метка имеет «динамическую» высоту
  2. Я также предполагаю, что если мы сделаем размер метки автоматически, чтобы растянуть высоту при увеличении ячейки, для изменения требуется только высота ячейки
  3. Я предполагаю, что у кончика есть соответствующее расстояние для того, где будет находиться этикетка и сколько места находится над и под ней
  4. Мы не хотим менять код каждый раз, когда меняем шрифт или положение метки в кончике

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

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

Вопросы:

  • Если вы не используете ячейку-прототип, вам нужно проверить, является ли ячейка нулевой, и инициализировать ее
  • Ваш перо / раскадровка должны иметь автоматический размер UILabel и иметь многострочное значение 0
4 голосов
/ 06 февраля 2010

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

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

3 голосов
/ 31 июля 2012

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

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

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

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

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

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [MyCell cellHeightForContent:yourContent];
}
3 голосов
/ 12 июля 2011

Чтобы ответить на вопрос, заданный оригинальным постером: «Можно ли вызывать cellForRowAtIndexPath?», Это не так.Это даст вам ячейку, но она НЕ будет распределять ее по этому indexPath внутри, и при этом она не будет помещена в очередь (нет способа вернуть ее обратно), так что вы просто потеряете ее.Я предполагаю, что он будет в пуле авто-выпусков и в конечном итоге будет удален, но вы все равно будете создавать множество ячеек снова и снова, и это действительно довольно расточительно.

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

1 голос
/ 21 августа 2012

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...