iOS. Приемы программирования — страница 31 из 124

• tableView: viewForHeaderInSection: — вызывается в делегате, когда табличному виду требуется отобразить заголовочный вид раздела. Каждый раздел табличного вида может содержать верхний колонтитул, некоторое количество ячеек и нижний колонтитул. В этой главе мы подробно обсудим все эти участки таблицы. Верхний и нижний колонтитул — это обычные экземпляры UIView. Данный метод является необязательным, но если вы хотите сконфигурировать заголовок для разделов вашего табличного вида, то пользуйтесь этим методом, чтобы создать экземпляр вида и передать его обратно в качестве возвращаемого значения. О верхних и нижних колонтитулах табличных видов подробнее рассказано в разделе 4.5.

• tableView: viewForFooterInSection: — делегатный метод, аналогичный tableView: viewForHeaderInSection:, но он возвращает вид с нижним колонтитулом таблицы. Как и заголовок, нижний колонтитул таблицы не является обязательным, но если он вам нужен, то его следует создавать здесь. Подробнее о верхних и нижних колонтитулах табличных видов рассказано в разделе 4.5.

• tableView: didEndDisplayingCell: forRowAtIndexPath: — вызывается в объекте-делегате, когда в ходе прокрутки таблицы ячейка уходит с экрана. Этот метод действительно очень удобен для вызова в делегате, так как вы можете удалять объекты и выбрасывать их из памяти, если эти объекты ассоциированы с ячейкой, которая ушла с экрана, а вы полагаете, что связанные с ней объекты вам больше не понадобятся.

• tableView: willDisplayCell: forRowAtIndexPath: — этот метод вызывается в делегате табличного вида всякий раз, когда ячейка вот-вот отобразится на экране.

Чтобы задать делегат для табличного вида, просто укажите в качестве значения свойства delegate экземпляра UITableView такой объект, который соответствует протоколу UITableViewDelegate. Если табличный вид является частью контроллера вида, то можно просто сделать этот контроллер делегатом вашего табличного вида, вот так:


#import «ViewController.h»


@interface ViewController () 

@property (nonatomic, strong) UITableView *myTableView;

@end


@implementation ViewController


— (void)viewDidLoad{

[super viewDidLoad];


self.myTableView = [[UITableView alloc]

initWithFrame: self.view.bounds

style: UITableViewStylePlain];


self.myTableView.delegate = self;


[self.view addSubview: self.myTableView];


}


@end


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

Объект-делегат обязан отвечать на сообщения, помеченные протоколом UITableViewDelegate как @required. Отвечать на другие сообщения не обязательно, но делегат должен отвечать на все сообщения, которые, по вашему замыслу, будут изменять табличный вид.

Сообщения, отправляемые объекту-делегату табличного вида, несут с собой параметр, который сообщает делегату, какой именно табличный вид инициировал данное событие в своем делегате. Это очень важно отметить, так как в определенных обстоятельствах вы можете разместить в одном объекте (как правило, в виде) более одной таблицы. Поэтому настоятельно рекомендую принимать соответствующие решения с учетом того, какой именно табличный вид послал конкретное сообщение объекту-делегату:


— (CGFloat) tableView:(UITableView *)tableView

heightForRowAtIndexPath:(NSIndexPath *)indexPath{

if ([tableView isEqual: self.myTableView]){

return 100.0f;

}

return 40.0f;

}

}


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

4.1. Наполнение табличного вида данными

Постановка задачи

Требуется наполнить табличный вид данными.

Решение

Необходимо создать объект, соответствующий протоколу

UITableViewDataSource
, и присвоить этот объект экземпляру табличного вида. Затем, отвечая на сообщения источника данных, предоставьте информацию для вашего вида. Продолжим данный пример и объявим. h-файл контроллера нашего вида. Позже в коде для этого вида будет создана таблица:


#import «ViewController.h»


static NSString *TableViewCellIdentifier = @"MyCells";


@interface ViewController () 

@property (nonatomic, strong) UITableView *myTableView;

@end


Экземпляр TableViewCellIdentifier содержит идентификаторы ячеек в виде статической строковой переменной. Как вы вскоре узнаете, каждая ячейка может иметь идентификатор, и это очень помогает при повторном использовании ячеек. Пока считайте эту переменную просто уникальным идентификатором всех ячеек табличного вида — на данном этапе этого достаточно.

В методе viewDidLoad контроллера вида создадим табличный вид и присвоим ему контроллер вида в качестве источника данных:


— (void)viewDidLoad{

[super viewDidLoad];


self.myTableView =

[[UITableView alloc] initWithFrame: self.view.bounds

style: UITableViewStylePlain];


[self.myTableView registerClass: [UITableViewCell class]

forCellReuseIdentifier: TableViewCellIdentifier];


self.myTableView.dataSource = self;


/* Убеждаемся, что табличный вид правильно масштабируется. */

self.myTableView.autoresizingMask =

UIViewAutoresizingFlexibleWidth |

UIViewAutoresizingFlexibleHeight;


[self.view addSubview: self.myTableView];


}


В этом фрагменте кода все элементарно, кроме метода registerClass: forCellReuseIdentifier:, который мы вызываем в экземпляре табличного вида. Что же делает этот метод? Параметр registerClass этого метода просто принимает имя класса, соответствующее типу объекта, который вы хотите загружать в табличном виде при отображении каждой ячейки. Все ячейки внутри табличного вида должны быть прямыми или непрямыми потомками класса UITableViewCell. Сам этот класс предоставляет программистам довольно широкий функционал. Но при желании этот класс можно и расширить — достаточно произвести от него подкласс, добавив к новому классу требуемый функционал. Итак, возвращаемся к параметру registerClass вышеупомянутого метода. Вам потребуется сообщить имя класса ячеек этому параметру, а потом передать идентификатор параметру forCellReuseIdentifier. Вот по какой причине мы ассоциируем классы табличного вида с идентификаторами: когда позже вы заполняете табличный вид данными, можете просто передать тот же самый идентификатор методу dequeueReusableCellWithIdentifier: forIndexPath: табличного вида, после чего приказать табличному виду инстанцировать ячейку таблицы, если в наличии нет ячеек, доступных для повторного использования. Все это просто отлично, так как в предыдущих версиях iOS SDK программистам приходилось инстанцировать эти ячейки самостоятельно, если из табличного вида не удавалось добыть уже готовый код, пригодный для повторного использования.

Теперь необходимо убедиться в том, что наш табличный вид реагирует на методы протокола UITableViewDataSource, помеченные как @required (обязательные). Нажмите на клавиатуре комбинацию клавиш Command+Shift+O, введите в диалоговое окно имя этого протокола, затем нажмите клавишу Enter. В результате вы увидите обязательные методы данного протокола.

Класс UITableView определяет свойство под названием dataSource. Это нетипизированный объект, который должен подчиняться протоколу UITableViewDataSource. Всякий раз, когда табличный вид обновляется и перезагружается с помощью метода reloadData, табличный вид будет вызывать в своем источнике данных различные методы, чтобы получить информацию о тех данных, которыми вы хотите заполнить таблицу. Источник данных табличного вида может реализовывать три важных метода, два из которых являются обязательными для любого источника данных:

• numberOfSectionsInTableView: — позволяет источнику данных информировать табличный вид о количестве разделов, которые должны быть загружены в таблицу;

• tableView: numberOfRowsInSection: — сообщает контроллеру вида, сколько ячеек или строк следует загрузить в каждый раздел. Номер раздела передается источнику данных в параметре numberOfRowsInSection. Реализация этого метода является обязательной для объекта источника данных;

• tableView: cellForRowAtIndexPath: — отвечает за возвращение экземпляров класса UITableViewCell как строк таблицы, которыми должен заполняться табличный вид. Реализация этого метода обязательна для объекта источника данных.

Итак, продолжим и реализуем эти методы в контроллере вида один за другим. Сначала сообщим табличному виду, что мы хотим отобразить три раздела:


— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{


if ([tableView isEqual: self.myTableView]){

return 3;

}


return 0;


}


Далее сообщим табличному виду, сколько строк хотим в нем отобразить для каждого раздела:


— (NSInteger)tableView:(UITableView *)tableView

numberOfRowsInSection:(NSInteger)section{


if ([tableView isEqual: self.myTableView]){

switch (section){

case 0:{

return 3;

break;

}

case 1:{

return 5;

break;

}

case 2:{

return 8;

break;

}

}

}

return 0;


}


Итак, на данный момент мы приказали табличному виду отобразить три раздела. В первом разделе три строки, во втором — пять, в третьем — восемь. Что дальше? Нужно вернуть табличному виду экземпляры UITableViewCell — тех ячеек, которые мы хотим отобразить в таблице:


— (UITableViewCell *) tableView:(UITableView *)tableView

cellForRowAtIndexPath:(NSIndexPath *)indexPath{


UITableViewCell *result = nil;


if ([tableView isEqual: self.myTableView]){


cell = [tableView

dequeueReusableCellWithIdentifier: TableViewCellIdentifier

forIndexPath: indexPath];


cell.textLabel.text = [NSString stringWithFormat:

@"Section %ld, Cell %ld",

(long)indexPath.section,

(long)indexPath.row];


}


return cell;


}


Теперь, если запустить приложение в эмуляторе iPhone, мы увидим результат работы (рис. 4.2).


Рис. 4.2. Обычный табличный вид с тремя разделами


Когда табличный вид перезагружается или обновляется, он запрашивает источник данных через протокол

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

После того как источник данных укажет количество разделов, табличный вид запросит количество строк, которые должны быть загружены в каждый из разделов. Источник данных получает индекс с нулевой базой для каждого раздела и на базе этого индекса решает, сколько ячеек загрузить в каждый раздел.

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

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

4.2. Использование дополнительных элементов в ячейке табличного вида