Продолжаю тему UITableView. В предыдущих статьях вы узнали как работать с UITableView, наполнять данными, создавать кастомные ячейки и какую ошибку не следует допускать. Одна из замечательных возможностей UITableView — это повторное использование ячеек. В этой статье я покажу одну из реализаций такого поведения. В качестве примера мы сделаем «кастомный» UITableView.

Суть метода — есть два множества ячеек. Первое содержит в себе ячейки, которые в данный момент находятся на экране. Второе множество содержит в себе ячейки которых нет на экране и которые могут быть использованы снова. При прокрутке таблицы мы каждый раз пересчитываем индексы ячеек, которые должны быть на экране. Если нужно удаляем ячейки, которые должны «спрятаться» и показываем новые (доставая их из второго множества).

Рекомендую загрузить исходники проекта по этой ссылке.

Первое, что сделаем — это создадим классы:

  • ATableView — нужно наследовать от UIScrollView. Это и будет наша таблица, в которой будут показываться ячейки
  • ATableViewCell — нужно наследовать от UIView. Данным классом будут представляться ячейки

Как и в настоящем UITableView нам нужен протокол для источника данных (dataSource), определим его:

@protocol ATableViewDataSource <NSObject>

@required
// количество ячеек в таблице
-(int) numberOfRowsInTableView:(ATableView *)tableView;
// конкретная ячейка
-(ATableViewCell*) tableView:(ATableView *)tableView cellForRow:(int)row;
@end

Как видим, первый метод должен вернуть количество ячеек. Второй должен вернуть конкретную ячейку. В отличие от UITableView, мы не используем NSIndexPath и секции.

Определимся, что высота ячейки фиксированная:

#define CELL_HEIGHT 50.0

Приведу код объявления класса ATableView:

@interface ATableView : UIScrollView<UIScrollViewDelegate>

{
NSMutableSet* m_recycledCells; // невидимые ячейки, готовые к повтороному использованию
NSMutableSet* m_visibleCells; // видимые ячейки
int m_numberOfRows; // количество ячеек в таблице
}
@property (nonatomic, assign) IBOutlet id<ATableViewDataSource> dataSource; // источник данных
- (void) reloadData;
- (ATableViewCell *)dequeueRecycledCell;
@end

Метод reloadData обновляет таблицу, запрашивая у источника данных количество ячеек и каждую конкретную ячейку. Метод dequeueRecycledCell достает из m_recycledCells ячейку, которую можно использовать повторно. Этот метод можно сравнить с методом UITableView:

- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;

Оба они делают одно и тоже — достают ячейку, которую можно использовать повторно. Разница лишь в том, что второй метод умеет различать ячейки по идентификатору (но смысла это не меняет).

Определим метод в ATableView, который будет пересчитывать номера видимых ячеек. Он будет называтся layoutCells. Можно назвать его самым главным методом, именно он позволит добится повторного использования. Далее его полная реализация, пояснение вы найдете сразу после кода:

-(void) layoutCells

{
// определим границы номеров ячеек, которые нужны показывать

// 1
CGRect bounds = self.bounds;

int firstCellIndex = floorf(CGRectGetMinY(bounds) / (1+CELL_HEIGHT));
int lastCellIndex = floorf((CGRectGetMaxY(bounds)-1) / (1+CELL_HEIGHT));
firstCellIndex = MAX(firstCellIndex, 0);
lastCellIndex = MIN(lastCellIndex, m_numberOfRows - 1);

// удалим ненужные ячейки
// 2
for (ATableViewCell *cell in m_visibleCells) {
if (cell.index < firstCellIndex || cell.index > lastCellIndex) {
[m_recycledCells addObject:cell];
[cell removeFromSuperview];
}
}

[m_visibleCells minusSet:m_recycledCells];

// добавим недостающие ячейки
// 3
for (int index = firstCellIndex; index <= lastCellIndex; index++) {
// если ячейки нет на экране
if (![self isDisplayingCellForIndex:index]) {
// 4
// вот тут просим у dataSource ячейку
ATableViewCell* cell = [self.dataSource tableView:self cellForRow:index];
cell.frame = CGRectMake(0, (CELL_HEIGHT+1)*index, self.bounds.size.width, CELL_HEIGHT);
cell.index = index;

// 5
[self addSubview:cell];

// чтобы при скролле ячейка не закрывала scrollbar
[self sendSubviewToBack:cell];

[m_visibleCells addObject:cell];
}
}
}

В (1) мы определяем индекс первой ячейки, которая должна быть на экране и индекс последней ячейки.

В (2) мы удаляем все ячейки с экрана, которые не должны быть на экране. Удаляются все ячейки, индексы которых не попадают под заданный диапазон. Все удаленные ячейки попадают в m_recycledCells и удаляются из m_visibleCells.

В (3) мы перебираем все ячейки, которые должны быть на экране. Затем в (4) мы проверяем, есть ли ячейка на экране. Если нет — добавляем. Ячейку мы запрашиваем у dataSource (точно так же, как и при использовании UITableView). В (5) мы ложим ячейку на экран и добавляем в m_visibleCells.

Приведенный метод нужно вызывать при прокрутке и при обновлении таблицы:

-(void) scrollViewDidScroll:(UIScrollView *)scrollView

{
[self layoutCells];
}

Не менее важный метод dequeueRecycledCell, который будет возаращать ячейку для повторного использования:

- (ATableViewCell *)dequeueRecycledCell

{
ATableViewCell *cell = [m_recycledCells anyObject];

if ( cell ) {
[[cell retain] autorelease];
[m_recycledCells removeObject:cell];
}

return cell;
}

Код ячейки (ATableViewCell) я приводить не буду, его можно увидеть в исходниках проекта, который можно скачать по ссылке в конце статьи. Рассмотрим, как нужно использовать данную таблицу. Протокол ATableViewDataSource требует реализации двух методов:

-(int) numberOfRowsInTableView:(ATableView *)tableView

{
return 15;
}
-(ATableViewCell*) tableView:(ATableView *)tableView cellForRow:(int)row
{
ATableViewCell* cell;

// 1
cell = [tableView dequeueRecycledCell];
if ( !cell ) {
// 2
cell = [[[ATableViewCell alloc] init] autorelease];
}

cell.titleLabel.text = [NSString stringWithFormat:@"Row %d",row];

return cell;
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

В (1) мы пытаемся запросить у ATableView ячейку, которую можно использовать повторно. И, если это не удается, мы создаем новую. Запустив проект мы можем убедится, что создается только 10 ячеек. Количество создаваемых ячеек не увеличится даже если мы заменим 15 на 150.

Подведем итоги. Мы рассмотрели метод схожий с методом, который использует UITableView. Я не утверждаю, что UITableView использует в точности такой же алгоритм, но цель их одинакова — не создавать все ячейки, а переиспользовать их. Что это нам дает? В первую очередь, конечно, прирост производительности и экономия памяти. Так же, данный метод можно использовать и для других целей, например при реализации просмотра картинок (как это делает приложение Photos).

Исходники проекта можно загрузить по этой ссылке.

Спасибо за внимание! Жду ваших комментариев, пожеланий и предложений!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *