iOS. Приемы программирования Нахавандипур Вандад
[self.window makeKeyAndVisible];
return YES;
}
Реализация контроллера сборного вида остается такой же, как в разделе 5.2. Если сейчас запустить приложение, то вы увидите просто черный экран, так как в стандартной реализации контроллера сборного вида фон вида даже не заменяется на белый. Пока нас это устраивает. Как минимум приложение уже не завершается аварийно, поскольку у нас уже есть объекты макета.
См. также
Разделы 5.1 и 5.2.
5.4. Наполнение сборного вида простейшим содержимым
Постановка задачи
Вы уже запрограммировали для вашего сборного вида макет с последовательной компоновкой, но пока не знаете, как отображать в нем ячейки.
Решение
Для представления ваших ячеек либо напрямую воспользуйтесь классом UICollectionViewCell, либо произведите от него подкласс, на базе которого уже сможете написать собственную реализацию. Кроме того, как будет показано далее, у вас может быть файл. xib, ассоциированный с ячейкой.
Обсуждение
В данном разделе я исхожу из того, что вы уже проработали разделы 5.1–5.3 и выполнили базовую настройку проекта.
Будем работать по порядку. Начнем с самого простого и самого быстрого способа создания ячеек. Инстанцируем объекты типа UICollectionViewCell и занесем их в сборный вид в нашем источнике данных. У класса UICollectionViewCell есть свойство вида с содержимым, называемое contentView, куда вы можете добавлять для отображения собственные виды. Кроме того, можете задавать и многие другие свойства ячейки, например цвет фона. Именно цветом фона мы и займемся в этом примере. Но перед тем, как начать, опишем, чего мы собираемся добиться в данном разделе, подробно объясним стоящие перед нами требования.
Мы собираемся запрограммировать сборный вид с последовательной компоновкой, в котором будут отображаться три секции. В каждой из секций будет находиться от 20 до 40 ячеек, причем в первой секции все ячейки красные, во второй — зеленые, в третьей — синие (рис. 5.5).
Рис. 5.5. Простой сборный вид с последовательной компоновкой, в котором отображаются три секции с ячейками разных цветов
Итак, начнем. В контроллере сборного вида создадим метод, который может возвращать массив из трех цветов. Далее присвоим эти цвета ячейкам из каждой секции:
/* У нас будет три секции, и для каждой из них мы определим свой цвет ячеек. Для представления цвета используются самые обычные экземпляры UIColor, которые мы позже применим к каждой из ячеек в соответствующих секциях */
— (NSArray *) allSectionColors{
static NSArray *allSectionColors = nil;
if (allSectionColors == nil){
allSectionColors = @[
[UIColor redColor],
[UIColor greenColor],
[UIColor blueColor],
];
}
return allSectionColors;
}
После этого переопределите выделенный инициализатор initWithCollectionViewLayout: вашего контроллера сборного вида и зарегистрируйте UICollectionViewCell с конкретным идентификатором. Не волнуйтесь, если пока не совсем улавливаете ход мысли, взгляните на ситуацию таким образом: для каждой ячейки, которую должен отобразить ваш сборный вид, программа сначала просмотрит очередь ячеек, доступных для повторного использования, и определит, есть ли в этой очереди устраивающие ее ячейки. Если они найдутся, сборный вид извлечет такие ячейки из очереди, в противном случае он создаст новую ячейку и вернет ее вам для выполнения конфигурации.
В более ранних версиях iOS приходилось вручную создавать ячейки, если табличному виду не удавалось найти готовые ячейки для повторного использования (сборные виды в ранних версиях iOS отсутствовали). Однако с появлением новых API Apple выполнила очень интересную работу, связанную с многократно используемыми ячейками. Компания предоставила новые API как для табличных, так и для сборных видов. Поэтому вы можете зарегистрировать вызов и с табличным видом, и со сборным видом. Если же вам приходится конфигурировать новую ячейку, то вы просто требуете от табличного или сборного вида новую ячейку нужного рода. Если такая ячейка имеется в очереди многократного использования, то она (ячейка) будет вам предоставлена. Если нет — то табличный или сборный вид автоматически создаст такую ячейку. Этот механизм называется регистрацией многоразовой ячейки, он реализуется двумя способами:
• регистрацией ячейки с использованием имени класса;
• регистрацией ячейки с использованием. xib-файла.
Оба этих способа регистрации многоразовых ячеек вполне хороши и отлично работают со сборными видами. Чтобы зарегистрировать новую ячейку для сборного вида, воспользовавшись ее именем класса, применяется метод registerClass: forCellWithReuseIdentifier: класса UICollectionView, где идентификатор — обычная строка, которую вы сообщаете сборному виду. При попытке получить многоразовые ячейки вы запрашиваете у сборного вида ячейку с заданным идентификатором. Чтобы зарегистрировать со сборным видом. xib-файл, необходимо использовать метод экземпляра registerNib: forCellWithReuseIdentifier: сборного вида. Идентификатор этого метода также вполне функционален, об этом рассказано ранее в данном абзаце.
Nib-файл — это объект типа UINib, с ним мы подробнее познакомимся далее в этой главе.
— (instancetype) initWithCollectionViewLayout:(UICollectionViewLayout *)layout{
self = [super initWithCollectionViewLayout: layout];
if (self!= nil){
/* Регистрируем со сборным видом ячейку для ее удобного получения */
[self.collectionView registerClass: [UICollectionViewCell class]
forCellWithReuseIdentifier: kCollectionViewCellIdentifier];
}
return self;
}
Как видите, в качестве идентификатора для ячеек мы используем константное значение kCollectionViewCellIdentifier. Необходимо определить его в контроллере вида:
#import «ViewController.h»
static NSString *kCollectionViewCellIdentifier = @"Cells";
@implementation ViewController
В стандартной реализации сборного вида будет содержаться всего одна секция, если только вы не реализуете в источнике данных метод numberOfSectionsInCollectionView:. В этом сборном виде мы хотим сделать три секции, так что реализуем его:
— (NSInteger)numberOfSectionsInCollectionView
:(UICollectionView *)collectionView{
return [self allSectionColors].count;
}
Одно из требований, предъявляемых к нашему приложению, такое: каждая секция должна содержать не менее 20, но не более 40 ячеек. Эту задачу можно решить с помощью функции arc4random_uniform(x). Она возвращает положительные целые числа в диапазоне от 0 до x, где x — параметр, который вы сообщаете этой функции. Следовательно, если требуется сгенерировать число в диапазоне от 20 до 40, всего лишь нужно прибавить 20 к возвращаемому значению этой функции, а значение x также сделать равным 20. Зная это, реализуем метод collectionView: numberOfItemsInSection: из источника данных сборного вида:
— (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section{
/* Генерируем от 20 до 40 ячеек для заполнения каждой секции */
return 20 + arc4random_uniform(21);
}
Наконец, требуется предоставить ячейки для сборного вида. Для этого реализуем метод collectionView: cellForItemAtIndexPath:, относящийся к источнику данных сборного вида:
— (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell =
[collectionView
dequeueReusableCellWithReuseIdentifier: kCollectionViewCellIdentifier
forIndexPath: indexPath];
cell.backgroundColor = [self allSectionColors][indexPath.section];
return cell;
}
Индексные пути просто содержат номер секции и номер строки. Поэтому индексный путь 0, 1 указывает, что речь идет о второй строке первой секции, поскольку индексы имеют нулевое основание. Если же мы захотим указать пятую строку десятой секции, то обозначим индексный путь как 9, 4. Индексные пути очень широко используются при работе с табличными и сборными видами, так как органично подходят для описания секций, каждая из которых наполнена ячейками. Делегаты и источники данных для табличных и сборных видов при работе указывают целевую ячейку именно по ее индексному пути. Например, если пользователь нажмет ячейку в сборном виде, то вы получите его индексный путь. С помощью этого индексного пути вы также сможете просмотреть базовую структуру данных конкретной ячейки (речь идет о данных, которые использовались в вашем классе для создания этой ячейки).
Как видите, здесь используется метод экземпляра dequeueReusableCellWithReuseIdentifier: forIndexPath:, относящийся к сборному виду. Этот метод применяется для извлечения многоразовых ячеек из очереди. Этот метод ожидает получения двух параметров: идентификатора ячейки, которую вы ранее зарегистрировали с этим сборным видом, а также индексного пути, по которому должна быть отображена ячейка. Индексный путь вы получаете в том же самом методе collectionView: cellForItemAtIndexPath: в качестве параметра, поэтому остается всего лишь сообщить идентификатор ячейки.
Возвращаемое значение этого метода представляет собой ячейку типа UICollectionViewCell, которую можно сконфигурировать. В данной реализации нам придется сделать всего одну вещь: задать в качестве фонового цвета ячейки тот цвет, который мы ранее выбрали для всех ячеек данной секции.
Итак, остался последний шаг перед завершением данного примера. Сделаем фон сборного вида белым, чтобы он выглядел немного лучше, чем со стандартным черным фоном. Реализуйте метод viewDidLoad контроллера сборного вида и задайте фоновый цвет для данного вида прямо в этом методе:
— (void) viewDidLoad{
[super viewDidLoad];
self.collectionView.backgroundColor = [UIColor whiteColor];
}
Экземпляр UICollectionViewController имеет вид типа UIView, к которому можно получить доступ по его свойству view. Не путайте этот вид со свойством collectionView вашего контроллера, соответствующим той сущности, в которой располагается сам сборный вид.
Красота решения, предложенного в данном разделе, заключается в том, что оно отлично работает и на iPad, и на iPhone. На рис. 5.5 показано, как результат выглядит на iPad, на рис. 5.6 — как на iPhone.
Рис. 5.6. Простой сборный вид, изображенный в эмуляторе iPhone
См. также
Разделы 5.1–5.3.
5.5. Заполнение сборных видов специальными ячейками с помощью XIB-файлов
Постановка задачи
Требуется сконфигурировать ячейки сборного вида в конструкторе интерфейса и заполнить ими сборный вид для последующего отображения.
Решение
Выполните следующие шаги.
1. Создайте подкласс UICollectionViewCell и назовите его (в данном примере мы будем использовать имя CollectionViewCell).
2. Создайте пустой. xib-файл и назовите его MyCollectionViewCell.xib.
3. Поместите в конструктор интерфейса ячейку сборного вида (ее вы найдете в библиотеке объектов). Она должна оказаться в вашем пустом. xib-файле (рис. 5.7). В конструкторе интерфейсов измените имя объекта-ячейки на MyCollectionViewCell (рис. 5.8). Поскольку вы устанавливаете такую ассоциацию, когда загружаете. xib-файл программно, специальный класс MyCollectionViewCell будет автоматически попадать в память. Волшебство, да и только!
Рис. 5.7. Объект пользовательского интерфейса «ячейка сборного вида» в библиотеке объектов конструктора интерфейса
Рис. 5.8. Присваивание специального класса. xib-файлу ячейке специального сборного вида
4. Оформите ячейку в конструкторе интерфейса. Необходимо гарантировать, что для каждого компонента пользовательского интерфейса, который вы помещаете в ячейку, создается также ассоциированный с ней объект IBOutlet, расположенный либо в заголовочном файле, либо в файле реализации вашего класса (MyCollectionViewCell).
5. Зарегистрируйте с вашим сборным видом. nib-файл, воспользовавшись для этого методом экземпляра registerNib: forCellWithReuseIdentifier: сборного вида. Чтобы загрузить. nib-файл в память, используется метод класса nibWithNibName: bundle:, относящийся к классу UINib. Об этом методе мы вскоре поговорим.
Обсуждение
Ранее в данном разделе вы узнали о том, что для специальной ячейки необходимо создать. xib-файл и назвать этот файл MyCollectionViewCell.xib. Не забывайте, что ваш. xib-файл может называться и совершенно иначе. Тем не менее ради простоты примера и для того, чтобы читатели видели во всей главе одно и то же соглашение об именованиях, будем пользоваться вышеупомянутым именем. Итак, продолжите и создайте пустой. xib-файл, выполнив следующие шаги.
1. Откройте File — New — File (Файл — Новый — Файл).
2. В левой части экрана в категории iOS выберите запись User Interface (Пользовательский интерфейс), а в правой части — вариант Empty (Пустой).
3. Теперь система запросит у вас семейство устройств, к которому относится данный. xib-файл. Здесь просто выберите iPhone.
4. Далее вам будет предложено сохранить. xib-файл на диск. Сохраните его под именем MyCollectionViewCell.xib.
Кроме того, потребуется создать класс, который вы сможете связать с содержимым. xib-файла. Этот класс мы назовем MyCollectionViewCell, он будет наследовать от UICollectionViewCell. Чтобы его создать, выполните следующие шаги.
1. Откройте File-New-File (Файл-Новый-Файл).
2. В диалоговом окне для создания нового файла в категории iOS выберите вариант Cocoa Touch. В правой части экрана выберите Objective-C class (Класс Objective-C).
3. Назовите класс MyCollectionViewCell и выберите UICollectionViewCell в качестве его класса-предка.
4. Когда вам будет предложено сохранить файл на диске, так и сделайте.
Теперь нужно ассоциировать созданный класс с. xib-файлом. Для этого выполните следующие шаги.
1. Откройте файл MyCollectionViewCell.xib в конструкторе интерфейса. В библиотеке объектов просто найдите объект Collection View Cell (Ячейка сборного вида) и поместите ее в. xib-файл. По умолчанию эта ячейка будет очень маленькой (50 50 точек) и будет иметь черный фон.
2. Явно выберите эту ячейку в вашем. xib-файле, щелкнув на ней. Откройте инспектор идентичности (Identity Inspector) в конструкторе интерфейса и измените значение поля Class (Класс) на MyCollectionViewCell, как было показано на рис. 5.8.
Далее следует добавить в ячейку нужные компоненты пользовательского интерфейса. Позже при заполнении сборного вида информацией значения этих компонентов можно будет изменить. Для данного примера лучше всего подойдет вид с изображением. Поэтому, когда у вас в конструкторе интерфейса открыт файл MyCollectionViewCell.xib, поместите в него экземпляр UIImageView. Подключите этот вид с изображением к заголовочному файлу вашей ячейки (MyCollectionViewCell.h) и назовите его iViewBckgroundImage, чтобы заголовочный файл ячейки выглядел примерно так:
#import <UIKit/UIKit.h>
@interface MyCollectionViewCell: UICollectionViewCell
@property (weak, nonatomic) IBOutlet UIImageView *iViewBackgroundImage;
@end
Мы собираемся заполнить этот вид разными изображениями. В этом разделе я создал для работы три простых изображения, каждое размером 50 50 точек. Вы можете пользоваться любыми другими картинками на ваш выбор — просто поищите их в Интернете. Когда найдете понравившиеся вам картинки, добавьте их в свой проект. Убедитесь, что изображения называются 1.png, 2.png и 3.png и что их увеличенные вдвое аналоги для сетчатого дисплея называются [email protected], [email protected] и [email protected].
В данном примере мы собираемся отобразить примерно такой же пользовательский интерфейс, как на рис. 5.5. Но мы будем задавать для ячеек не цвета, а фоновые изображения, причем случайные. Поэтому целесообразно использовать в качестве основы для данного примера тот код, который мы написали в разделе 5.4, так как результат будет очень похожим.
Первое изменение, которое предстоит внести, заключается в написании метода, с помощью которого мы будем возвращать случайное изображение. Как было объяснено ранее, у нас есть массив изображений. После инстанцирования массива нам понадобится удобный небольшой метод, который будет выбирать из массива случайное изображение:
— (NSArray *) allImages{
static NSArray *AllSectionImages = nil;
if (AllSectionImages == nil){
AllSectionImages = @[
[UIImage iNamed:@"1"],
[UIImage iNamed:@"2"],
[UIImage iNamed:@"3"]
];
}
return AllSectionImages;
}
— (UIImage *) randomImage{
return [self allImages][arc4random_uniform([self allImages].count)];
}
Далее потребуется переопределить выделенный метод-инициализатор контроллера сборного вида, чтобы зарегистрировать. nib-файл MyCollectionViewCell с этим сборным видом:
— (instancetype) initWithCollectionViewLayout:(UICollectionViewLayout *)layout{
self = [super initWithCollectionViewLayout: layout];
if (self!= nil){
/* Регистрируем nib-файл со сборным видом для удобства получения информации */
UINib *nib = [UINib nibWithNibName:
NSStringFromClass([MyCollectionViewCell class])
bundle: [NSBundle mainBundle]];
[self.collectionView registerNib: nib
forCellWithReuseIdentifier: kCollectionViewCellIdentifier];
}
return self;
}
В ответ на запрос о том, сколько у нас секций, также возвратим случайное число в диапазоне от 3 до 6. Это требование не является обязательным — мы вполне могли бы обойтись и одной секцией, но если их будет больше, это точно не помешает. Кроме того, в каждой секции должно быть от 10 до 15 ячеек:
— (NSInteger)numberOfSectionsInCollectionView
:(UICollectionView *)collectionView{
/* От 3 до 6 секций */
return 3 + arc4random_uniform(4);
}
— (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section{
/* В каждой секции — от 10 до 15 ячеек */
return 10 + arc4random_uniform(6);
}
Наконец, запросим у сборного вида ячейки, а затем сконфигурируем их со случайными фоновыми изображениями:
— (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCollectionViewCell *cell =
[collectionView
dequeueReusableCellWithReuseIdentifier: kCollectionViewCellIdentifier
forIndexPath: indexPath];
cell.iViewBackgroundImage.i = [self randomImage];
cell.iViewBackgroundImage.contentMode = UIViewContentModeScaleAspectFit;
return cell;
}
Как видите, мы используем специальный класс MyCollectionViewCell в контроллере сборного вида. Чтобы программа успешно скомпилировалась, необходимо включить заголовочный файл ячейки в реализацию контроллера вида, вот так:
#import «ViewController.h»
#import «MyCollectionViewCell.h»
…
Запустив приложение, вы увидите примерно такую картинку, как на рис. 5.9. Разумеется, если вы используете в примере другие изображения, она будет другой, но у меня тут показаны нотки.
Рис. 5.9. Сборный вид со специальными ячейками, загруженными из. nib-файла
См. также
Раздел 5.4.
5.6. Обработка событий в сборных видах
Постановка задачи
Необходимо обрабатывать события, происходящие в сборных видах, например касания.
Решение
Присвойте делегат сборному виду. В других случаях не придется делать даже этого. Порой достаточно просто слушать интересующие вас события в классах ячеек и обрабатывать их прямо в этих классах.
Обсуждение
У сборных видов есть свойства delegate, которые должны соответствовать протоколу UICollectionViewDelegate. Делегатный объект, создаваемый с их помощью, будет получать различные делегатные вызовы от сборного вида, сообщающего делегату о различных событиях, например о том, что элемент был подсвечен или выделен. Необходимо различать подсвеченное и выделенное состояние ячейки сборного вида. Когда пользователь нажимает пальцем на ячейку сборного вида, но не поднимает палец сразу после этого, ячейка под пальцем становится подсвеченной. Когда пользователь нажимает на ячейку, а затем сразу поднимает палец (это означает, что он хочет совершить с ячейкой какое-то действие), данная ячейка становится выделенной.
Ячейки сборных видов, относящиеся к типу UICollectionViewCell, имеют два очень полезных свойства — highlighted и selected.
Если вы хотите всего лишь изменить визуальное оформление вашей ячейки, когда она выделена, то задача тем более упрощается, поскольку ячейки типа UICollectionViewCell предоставляют свойство selectedBackgroundView типа UIView. В качестве значения этого свойства можно задать любой валидный вид. Затем этот вид отобразится на экране, как только ячейка будет выделена. Продемонстрируем эти возможности на основе кода, который мы написали в разделе 5.5. Как вы помните, там мы создали специальную ячейку, одно из свойств которой (iViewBackgroundImage) снабжало ее фоновым изображением. Изображение заполняло весь фон ячейки. В этот вид с изображением мы загружали специально подобранные картинки. Теперь мы собираемся залить фон ячейки голубым цветом, как только она будет выделена. Поскольку вид с изображением находится поверх всех остальных компонентов сборного вида, перед заданием фонового цвета нам придется гарантировать, что этот вид с изображением будет прозрачным. Для этого оттенок фона вида с изображением нужно изменить на проницаемый. Дело в том, что по умолчанию фон у вида с изображением непрозрачный, поэтому если расположить такой вид поверх другого вида, имеющего фоновый цвет, то этот фоновый цвет, естественно, виден не будет. Соответственно, чтобы оставался виден фоновый цвет того вида, который является вышестоящим для вида с изображнием, фон самого вида с изображением должен быть прозрачным. Итак, начнем:
#import «MyCollectionViewCell.h»
@implementation MyCollectionViewCell
— (void) awakeFromNib{
[super awakeFromNib];
self.iViewBackgroundImage.backgroundColor = [UIColor clearColor];
self.selectedBackgroundView = [[UIView alloc] initWithFrame: self.bounds];
self.selectedBackgroundView.backgroundColor = [UIColor blueColor];
}
@end
Вот и все! Теперь если нажать любую ячейку в вашей программе, она сразу приобретет голубой цвет фона.
Конечно, есть и другие операции, для выполнения которых требуется слушать различные события, происходящие в сборном виде. Например, может понадобиться воспроизвести звук или анимацию, как только оказывается выделенной ячейка. Допустим, когда пользователь прикасается к ячейке на экране, мы хотим задействовать следующую анимацию: немедленно скрыть ячейку, а потом снова ее отобразить. Эта анимация повторяется с очень высокой частотой, в результате чего ячейка постепенно вырисовывается или постепенно исчезает из виду. Если мы хотим добиться именно такого эффекта, для начала зададим делегат для нашего сборного вида, так как в описанном сценарии мы действительно будем получать от вида множество событий. Как было указано ранее, ваш делегатный объект должен соответствовать протоколу UICollectionViewDelegate. В этом протоколе есть несколько полезных методов, которые мы можем реализовать. Далее перечислены некоторые важнейшие методы этого протокола.
Протокол UICollectionViewDelegateFlowLayout, как и рассмотренный нами в главе 4 протокол UITableViewDelegate, позволяет сообщать информацию о ваших элементах — например, значения их высоты и ширины, — а потом передавать эти значения макету с последовательной компоновкой. Можно сразу предоставить для всех элементов такого макета обобщенное значение размера, тогда они получатся одинаковыми. Другой вариант — реагировать на соответствующие сообщения, которые вы будете получать от протокола делегата макета с последовательной компоновкой. В этих сообщениях программа будет запрашивать у вас значения размеров для тех или иных ячеек в макете.
• collectionView: didHighlightItemAtIndexPath: — вызывается в делегате, когда ячейка подсвечивается.
• collectionView: didUnhighlightItemAtIndexPath: — вызывается в делегате, когда ячейка выходит из подсвеченного состояния. Этот метод срабатывает, когда пользователь успешно завершает событие касания (попадает пальцем по нужному элементу, а потом поднимает палец, совершая, таким образом, жест касания). В другом случае этот метод срабатывает, когда пользователь отменяет сделанное ранее выделение, выводя палец за пределы ячейки.
• collectionView: didSelectItemAtIndexPath: — этот метод вызывается в делегатном объекте, когда конкретная ячейка становится выделенной. Ячейка всегда является подсвеченной, перед тем как стать выделенной.
• collectionView: didDeselectItemAtIndexPath: — вызывается в делегате, когда ячейка выходит из выделенного состояния.
Итак, напишем приложение в соответствии с изложенными выше требованиями. Мы хотим, чтобы ячейка «развоплощалась», а потом вновь «вырисовывалась» на экране, когда ее выделяют. В экземпляре UICollectionViewController реализуем метод collectionView: didSelectItemAtIndexPath:, вот так:
#import «ViewController.h»