Изменение высоты ячейки в UITableView «на лету» и еще...
Привет, мир. Задача следующая. Есть таблица UITableView. Нужно, чтобы высота ячейки для этой таблицы менялась без reloadData, когда мы нажимаем на ячейку. Будем считать, что у ячейки два состояния: открыта и закрыта. В закрытом состоянии высота всех ячеек одинакова. В открытом — может отличаться. Создадим пустой проект на основе шаблона Window-based Application. Добавим к проекту TableViewController, унаследованный от UIViewController. Можно и сразу от UITableViewController, но я предпочитаю так не делать. Почему именно, пожалуй, тема для отдельного поста.
#import@interface TableViewController : UIViewController { UITableView *braveTableView; NSMutableArray *openedCells; } @property (nonatomic, retain) UITableView *braveTableView; @property (nonatomic, retain) NSMutableArray *openedCells; - (void)tableView:(UITableView *)tableView updateAtIndexPath:(NSIndexPath *)indexPath; @end
Завели в TableViewController поле UITableView с именем braveTableView. Эта UITableView отважна, потому что без страха смотрит в будущее и готова к переменам. Помимо отважной таблицы, имеется массив, который будет хранить состояние ячеек. Добавили метод, который будет обновлять таблицу для заданного indexPath.
Рассмотрим методы, относящиеся к UITableViewDataSource и UITableViewDelegate. В таблице будет 1 секция и 42 ячейки. По умолчанию высота ячейки будет 45. Но если ячейка открыта, высота ее будет случайной, от 45 до 100.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { CGFloat cellHeight = 45.0f; BOOL isCellOpen = [[self.openedCells objectAtIndex:indexPath.row] boolValue]; if (isCellOpen) { cellHeight = ((arc4random() % 55) + 45.0f); } return cellHeight; }
При нажатии на ячейку выполняем следующие действия. Эффектно убираем эффект выделения. Меняем состояние ячейки на противоположное. Обновляем нужную ячейку.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; BOOL currentState = [[self.openedCells objectAtIndex:indexPath.row] boolValue]; [self.openedCells replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithBool:!currentState]]; [self tableView:tableView updateAtIndexPath:indexPath]; }
- (void)tableView:(UITableView *)tableView updateAtIndexPath:(NSIndexPath *)indexPath { [UIView setAnimationsEnabled:NO]; [tableView beginUpdates]; [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationNone]; [tableView endUpdates]; [UIView setAnimationsEnabled:[UIView areAnimationsEnabled]]; }
Если все subview уже добавлены на ячейку и максимум, что нужно, изменить их видимость, то этого достаточно. Не понадобится даже reloadRowsAtIndexPaths. Попробуйте сделать так, чтобы текст в ячейке соответствовал текущему значению ее высоты. Попытка поместить код в heightForRowAtIndexPath или cellForRowAtIndexPath ни к чему не приведет. У вас не будет актуального значения. Зато с задачей справится willDisplayCell.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { cell.textLabel.text = [NSString stringWithFormat:@"%.0f", cell.frame.size.height]; }
Несколько расширим исходную задачу и поговорим еще о динамической высоте (звучит-то как!). Есть минимум два случая, когда нужно вычислить, сколько места займет содержимое: когда мы хотим узнать размеры UILabel для какого-то текста, набранного некоторым шрифтом; и когда мы хотим узнать размеры UIWebView, когда в него загружена страница, предполагая, конечно, что нас не устроит наличие прокрутки.
Вот вариант для UILabel. Функция возвращает размер, необходимый для размещения текста. Сам текст, шрифт и правила окончания строк настраиваются. Функция крайне полезна. Вспомните о ней, например, когда нужно будет отображать знак рубля после цены. Среди символов знака рубля нет. Обычно его реализуют картинкой. Для UILabel делают запас ширины, выравнивают текст по правому краю, приставляют картинку. Это решение. Но если выравнивать по левому краю и не хочется, чтобы была дырка, то функция expectedLabelSize спешит на помощь. Почти как Чип и Дейл.
- (CGSize)expectedLabelSize:(UILabel *)aLabel maximumLabelSize:(CGSize)aMaximumLabelSize { return [aLabel.text sizeWithFont:aLabel.font constrainedToSize:aMaximumLabelSize lineBreakMode:aLabel.lineBreakMode]; }
С UIWebView все чуть сложнее. Но немного. Считаем, что сам HTML для выполнения загружен. Допустим, загружен с помощью loadHTMLString. Ловим событие webViewDidFinishLoad из UIWebViewDelegate.
- (void)webViewDidFinishLoad:(UIWebView *)aWebView { CGRect frame; frame = aWebView.frame; frame.size.height = 1; aWebView.frame = frame; CGSize fittenSize = [aWebView sizeThatFits:CGSizeZero]; frame.size = fittenSize; frame.size.width = 320; aWebView.frame = frame; }
Что имеем на выходе. Для некоторого объекта класса UIWebView будет задан новый frame. Причем в данном случае frame будет расти только в высоту (точнее, вниз от заданных координат), а по ширине он будет ограничен и равен 320. Если вы хотите, чтобы он подстраивался под содержимое, изменяя и ширину, и высоту, уберите соответствующее присваивание. Принцип, думаю, понятен.
Вот так слово за слово и рассмотрели некоторые моменты по вычислению размеров «на лету». Исходный код проекта DynamicCellHeight (около 30 Кб) прилагаю, как обещал.
Комментарии
alex s
Статья супер! Благодарю! Очень выручили!