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

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

Необходимо предоставить пользователям приложения возможность без труда удалять строки из табличного вида.

Решение

Реализуйте в делегате табличного вида селектор

tableView: editingStyleForRowAtIndexPath:
, а в источнике данных табличного вида — селектор
tableView: commitEditingStyle: forRowAtIndexPath::


— (UITableViewCellEditingStyle)tableView:(UITableView *)tableView

editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{


return UITableViewCellEditingStyleDelete;


}


— (void) setEditing:(BOOL)editing

animated:(BOOL)animated{


[super setEditing: editing

animated: animated];


[self.myTableView setEditing: editing

animated: animated];


}


— (void) tableView:(UITableView *)tableView

commitEditingStyle:(UITableViewCellEditingStyle)editingStyle

forRowAtIndexPath:(NSIndexPath *)indexPath{


if (editingStyle == UITableViewCellEditingStyleDelete){


/* Сначала удаляем этот объект из источника данных */

[self.allRows removeObjectAtIndex: indexPath.row];


/* Потом удаляем ассоциированную с ним ячейку из табличного вида */

[tableView deleteRowsAtIndexPaths:@[indexPath]

withRowAnimation: UITableViewRowAnimationLeft];


}


}


Метод

tableView: editingStyleForRowAtIndexPath
: позволяет выполнять операции удаления. Он вызывается табличным видом, а его возвращаемое значение определяет, какие операции пользователь может делать в табличном виде (вставлять информацию, удалять информацию и т. д.). Метод
tableView: commitEditingStyle: forRowAtIndexPath:
выполняет затребованную пользователем операцию удаления. Второй из указанных методов определяется в делегате, но его функционал несколько перегружен: этот метод применяется не только для удаления данных, но и для удаления строк из таблицы.

Обсуждение

Табличный вид реагирует на жест смахивания (Swipe), отображая кнопку в правой части затронутой строки (рис. 4.5). Как видите, табличный вид не находится в режиме редактирования, но эта кнопка позволяет пользователю удалить строку.


Рис. 4.5. Кнопка для удаления, появляющаяся в ячейке табличного вида


Такой режим активизируется путем реализации метода

tableView: editingStyleForRowAtIndexPath:
(определяемого в протоколе
UITableViewDelegate
), чье возвращаемое значение указывает, будут ли в таблице разрешаться операции вставки, или удаления, или обе эти операции, или ни одна из них. Реализуя метод
tableView: commitEditingStyle: forRowAtIndexPath:
в источнике данных табличного вида, можно также получать уведомление о том, какую операцию выполнил пользователь, вставку или удаление.

Второй параметр метода

deleteRowsAtIndexPaths: withRowAnimation:
позволяет указывать метод анимации, который будет выполняться при удалении строк из табличного вида. В примере мы задали, что удаляемые строки будут уходить с экрана в направлении справа налево.

4.5. Создание верхних и нижних колонтитулов в табличных видах

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

Необходимо создать в таблице верхний и/или нижний колонтитул.

Решение

Создайте вид (это может быть подпись, вид с изображением или любой другой класс, прямо или опосредованно производимый от UIView) и присвойте этот вид верхнему и/или нижнему колонтитулу табличного раздела. Кроме того, как вы вскоре увидите, для верхнего или нижнего колонтитулов можно выделять конкретное количество точек.

Обсуждение

Табличный вид может иметь несколько верхних и нижних колонтитулов. У каждого раздела табличного вида может быть свой верхний и нижний колонтитул, так что если у вас в табличном виде три раздела, то в нем может быть максимум три верхних и три нижних колонтитула. Вы не обязаны создавать верхние и нижние колонтитулы в каком-либо из разделов и сами решаете, сообщать или нет табличному виду, что в определенном его разделе будут верхний и нижний колонтитулы. Эти виды-колонтитулы передаются табличному виду через его делегат — если вы решите их сделать. Верхние и нижние колонтитулы становятся частью табличного вида. Это означает, что, когда содержимое таблицы прокручивается, одновременно с ним прокручиваются и колонтитулы табличных разделов. Рассмотрим примеры верхнего и нижнего колонтитулов в табличном виде (рис. 4.6).


Рис. 4.6. Нижний колонтитул в верхнем разделе и верхний колонтитул Shortcuts (Быстрый доступ) в последнем разделе табличного вида


Как видите, в верхнем разделе (там, где находятся элементы Check Spelling (Проверка правописания) и Enable Caps Lock (Зафиксировать верхний регистр)) в нижнем колонтитуле написано: Double tapping the space bar will insert a period followed by a space (Двойное нажатие клавиши пробела вставляет точку, за которой следует пробел). Это нижний колонтитул верхнего раздела рассматриваемого вида. Причина, по которой этот фрагмент находится именно в нижнем, а не в верхнем колонтитуле, в том, что он прикреплен к нижней, а не к верхней части раздела. В последнем разделе данной таблицы также есть верхний колонтитул, на котором написано Shortcuts (Быстрый доступ). Здесь, наоборот, колонтитул является верхним, а не нижним, так как он прикреплен к верхней части раздела.

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

Идем дальше. Создадим простое приложение, внутри которого будет табличный вид. Потом сделаем две метки типа UILabel, одна будет играть роль верхнего колонтитула, а другая — нижнего в единственном разделе нашего табличного вида. Этот раздел будет заполнен всего тремя ячейками. В верхнем колонтитуле мы напишем Section 1 Header (Верхний колонтитул раздела 1), а в нижнем — Section 1 Footer (Нижний колонтитул раздела 1). Начнем с файла реализации контроллера вида, где определим табличный вид:


#import «ViewController.h»


static NSString *CellIdentifier = @"CellIdentifier";


@interface ViewController () 

@property (nonatomic, strong) UITableView *myTableView;

@end


@implementation ViewController

После этого создадим сгруппированный табличный вид и загрузим в него три ячейки:

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

cellForRowAtIndexPath:(NSIndexPath *)indexPath{


UITableViewCell *cell = nil;


cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier

forIndexPath: indexPath];


cell.textLabel.text = [[NSString alloc] initWithFormat:@"Cell %ld",

(long)indexPath.row];


return cell;


}


— (NSInteger) tableView:(UITableView *)tableView

numberOfRowsInSection:(NSInteger)section{

return 3;

}

— (void)viewDidLoad{

[super viewDidLoad];


self.myTableView =

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

style: UITableViewStyleGrouped];

[self.myTableView registerClass: [UITableViewCell class]

forCellReuseIdentifier: CellIdentifier];


self.myTableView.dataSource = self;

self.myTableView.delegate = self;

self.myTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth |

UIViewAutoresizingFlexibleHeight;


[self.view addSubview: self.myTableView];


}


И тут начинается самое интересное. Мы можем воспользоваться двумя важными методами (определяемыми в протоколе UITableViewDelegate), чтобы сделать метку и для верхнего и для нижнего колонтитула того раздела, который мы загрузили в табличный вид. Вот эти методы:

• tableView: viewForHeaderInSection: — ожидает возвращаемого значения типа UIView. Вид, возвращаемый этим методом, отобразится как верхний колонтитул раздела и будет указан в параметре viewForHeaderInSection;

• tableView: viewForFooterInSection: — ожидает возвращаемого значения типа UIView. Вид, возвращаемый этим методом, отобразится как нижний колонтитул раздела и будет указан в параметре viewForFooterInSection.

Теперь наша задача заключается в том, чтобы реализовать эти методы и вернуть экземпляр UILabel. На метке верхнего колонтитула мы укажем текст Section 1 Header (Верхний колонтитул раздела 1), а на метке нижнего — Section 1 Footer (Нижний колонтитул раздела 1), как и планировали:


— (UILabel *) newLabelWithTitle:(NSString *)paramTitle{

UILabel *label = [[UILabel alloc] initWithFrame: CGRectZero];

label.text = paramTitle;

label.backgroundColor = [UIColor clearColor];

[label sizeToFit];

return label;

}


— (UIView *) tableView:(UITableView *)tableView

viewForHeaderInSection:(NSInteger)section{


if (section == 0){

return [self newLabelWithTitle:@"Section 1 Header"];

}


return nil;


}


— (UIView *) tableView:(UITableView *)tableView

viewForFooterInSection:(NSInteger)section{


if (section == 0){

return [self newLabelWithTitle:@"Section 1 Footer"];

}


return nil;


}


Если теперь запустить приложение в эмуляторе, получится такая картинка, как на рис. 4.7.


Рис. 4.7. Метки для верхнего и нижнего колонтитулов табличного вида, выровненные неправильно


Причина такого неправильного выравнивания в том, что родительский вид не знает высоты видов-меток. Для указания высоты видов верхнего и нижнего колонтитулов следует использовать два следующих метода, определяемых в протоколе UITableViewDelegate:

• tableView: heightForHeaderInSection: — возвращаемое значение данного метода относится к типу CGFloat. Оно указывает высоту верхнего колонтитула раздела табличного вида. Индекс раздела передается в параметре heightForHeaderInSection;

• tableView: heightForFooterInSection: — возвращаемое значение данного метода относится к типу CGFloat. Оно указывает высоту нижнего колонтитула раздела табличного вида. Индекс раздела передается в параметре heightForHeaderInSection.


— (CGFloat) tableView:(UITableView *)tableView

heightForHeaderInSection:(NSInteger)section{


if (section == 0){

return 30.0f;

}

return 0.0f;

}


— (CGFloat) tableView:(UITableView *)tableView

heightForFooterInSection:(NSInteger)section{


if (section == 0){

return 30.0f;

}


return 0.0f;


}


Запустив это приложение, вы увидите, что теперь метки верхнего и нижнего колонтитулов имеют фиксированную высоту. Но в написанном нами коде все еще остается какая-то ошибка — дело в левом поле меток верхнего и нижнего колонтитулов. В этом можно убедиться, взглянув на рис. 4.8.


Рис. 4.8. Левые поля меток в верхнем и нижнем колонтитулах — неправильные


Причина заключается в том, что по умолчанию табличный вид размещает верхний и нижний колонтитулы в точке с координатой 0.0f по оси Х. Можно подумать, что эта проблема решается изменением контуров меток верхнего и нижнего колонтитулов, но, к сожалению, это мнение ошибочно. Проблема решается созданием универсального вида UIView, где и размещаются метки для верхнего и нижнего колонтитулов. Возвратите в качестве верхнего/нижнего колонтитула такой универсальный вид, но измените положение меток по оси Х в этом виде.

Теперь изменим реализацию методов tableView: viewForHeaderInSection: и tableView: viewForFooterInSection::


— (UIView *) tableView:(UITableView *)tableView

viewForHeaderInSection:(NSInteger)section{

UIView *header = nil;

if (section == 0){

UILabel *label = [self newLabelWithTitle:@"Section 1 Header"];

/* Перемещаем метку на 10 точек вправо. */

label.frame = CGRectMake(label.frame.origin.x + 10.0f,

5.0f, /* Опускаемся на 5 точек вниз

по оси y. */

label.frame.size.width,

label.frame.size.height);


/* Делаем ширину содержащего вида на 10 точек больше,

чем ширина метки, так как для метки требуется

10 дополнительных точек ширины в левом поле. */


CGRect resultFrame = CGRectMake(0.0f,

0.0f,

label.frame.size.width + 10.0f,

label.frame.size.height);

header = [[UIView alloc] initWithFrame: resultFrame];

[header addSubview: label];


}


return header;


}


— (UIView *) tableView:(UITableView *)tableView

viewForFooterInSection:(NSInteger)section{


UIView *footer = nil;

if (section == 0){


UILabel *label = [[UILabel alloc] initWithFrame: CGRectZero];


/* Перемещаем метку на 10 точек вправо. */

label.frame = CGRectMake(label.frame.origin.x + 10.0f,

5.0f, /* Опускаемся на 5 точек вниз по оси y*/

label.frame.size.width,

label.frame.size.height);


/* Делаем ширину содержащего вида на 10 точек больше,

чем ширина метки, так как для метки требуется

10 дополнительных точек ширины в левом поле. */

CGRect resultFrame = CGRectMake(0.0f,

0.0f,

label.frame.size.width + 10.0f,

label.frame.size.height);

footer = [[UIView alloc] initWithFrame: resultFrame];

[footer addSubview: label];


}


return footer;


}


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


Рис. 4.9. В табличном виде отображаются метки верхнего и нижнего колонтитулов


Пользуясь изученными здесь методами, вы также можете размещать изображения в верхнем и нижнем колонтитулах табличных видов. Экземпляры класса UIImageView являются производными от класса UIView, поэтому вы легко можете ставить картинки в виды для изображений и возвращать их как верхние/нижние колонтитулы табличного вида. Если вы не собираетесь помещать в верхних и нижних колонтитулах табличных видов ничего, кроме текста, то можете пользоваться двумя удобными методами, определяемыми в протоколе UITableViewDataSource. Эти методы избавят вас от массы проблем. Чтобы не создавать собственные метки и не возвращать их как верхние/нижние колонтитулы табличного вида, просто пользуйтесь следующими методами:

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

• tableView: titleForFooterInSection: — возвращаемое значение этого метода относится к типу NSString. Табличный вид будет автоматически помещать в метке строку, которая будет отображаться как нижний колонтитул раздела, указываемый в параметре titleForFooterInSection.

Итак, чтобы упростить код приложения, избавимся от реализаций методов tableView: viewForHeaderInSection: и tableView: viewForFooterInSection:, заменив их реализациями методов tableView: titleForHeaderInSection: и tableView: titleForFooterInSection::


— (NSString *) tableView:(UITableView *)tableView

titleForHeaderInSection:(NSInteger)section{


if (section == 0){

return @"Section 1 Header";

}


return nil;


}

— (NSString *) tableView:(UITableView *)tableView

titleForFooterInSection:(NSInteger)section{


if (section == 0){

return @"Section 1 Footer";

}


return nil;


}


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


Рис. 4.10. Табличный вид, в верхнем и нижнем колонтитулах которого отображается текст

4.6. Отображение контекстных меню в ячейках табличных видов