iOS. Приемы программирования Нахавандипур Вандад
#import «MyCollectionViewCell.h»
static NSString *kCollectionViewCellIdentifier = @"Cells";
@implementation ViewController
— (void) collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *selectedCell =
[collectionView cellForItemAtIndexPath: indexPath];
const NSTimeInterval kAnimationDuration = 0.20;
[UIView animateWithDuration: kAnimationDuration animations: ^{
selectedCell.alpha = 0.0f;
} completion: ^(BOOL finished) {
[UIView animateWithDuration: kAnimationDuration animations: ^{
selectedCell.alpha = 1.0f;
}];
}];
}
…
Мы пишем этот код в контроллере сборного вида, который по умолчанию выбирается системой и в качестве источника данных, и в качестве делегата этого сборного вида. Он соответствует протоколам UICollectionViewDataSource и UICollectionViewDelegate. Следовательно, вы просто можете реализовать любой метод делегата или источника данных прямо в файле реализации вашего контроллера сборного вида.
В примере выше мы используем анимацию, но это не самое подходящее место, чтобы объяснять принципы работы анимации. Если вы хотите подробнее изучить, как в iOS создается простая анимация, обратитесь к главе 17 этой книги.
Итак, тут все было просто. Рассмотрим другой пример. Допустим, когда ячейка подсвечивается, мы хотим сделать ее вдвое крупнее обычного ее размера, а при выходе этой ячейки из подсвеченного состояния вернуть ей исходный размер. Таким образом, когда пользователь прикасается пальцем к ячейке (но еще не поднимает палец), ячейка увеличивается вдвое, а когда пользователь убирает палец — вновь уменьшается, тоже вдвое. Для этого нам потребуется реализовать в контроллере сборного вида методы collectionView: didHighlightItemAtIndexPath: и collectionView: didUnhighlightItemAtIndexPath: из протокола UICollectionViewDelegate. Как вы помните, контроллеры сборных видов по умолчанию соответствуют протоколам UICollectionViewDelegate и UICollectionViewDataSource:
#import «ViewController.h»
#import «MyCollectionViewCell.h»
static NSString *kCollectionViewCellIdentifier = @"Cells";
const NSTimeInterval kAnimationDuration = 0.20;
@implementation ViewController
— (void) collectionView:(UICollectionView *)collectionView
didHighlightItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *selectedCell =
[collectionView cellForItemAtIndexPath: indexPath];
[UIView animateWithDuration: kAnimationDuration animations: ^{
selectedCell.transform = CGAffineTransformMakeScale(2.0f, 2.0f);
}];
}
— (void) collectionView:(UICollectionView *)collectionView
didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *selectedCell =
[collectionView cellForItemAtIndexPath: indexPath];
[UIView animateWithDuration: kAnimationDuration animations: ^{
selectedCell.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
}];
}
…
Как видите, мы используем функцию CGAffineTransformMakeScale из фреймворка Core Graphics для создания аффинного преобразования, а потом присваиваем это преобразование самой ячейке. Достигается нужный эффект: сначала ячейка увеличивается вдвое, а потом уменьшается до исходного размера. Эта функция подробнее описана в разделе 17.12.
См. также
Разделы 5.2, 5.3, 5.5, 17.12.
5.7. Создание верхних и нижних колонтитулов в макете с последовательной компоновкой
Постановка задачи
Требуется создать в сборном виде отдельные виды для верхнего и нижнего колонтитулов, так же как в табличном виде. При этом используется последовательная компоновка.
Решение
Выполните следующие шаги.
1. Создайте по файлу. xib для верхнего и для нижнего колонтитулов.
2. Найдите в библиотеке объектов конструктора интерфейса по одному объекту Collection Reusable View и перетащите их в ваши. xib-файлы. Убедитесь, что эти многоразовые сборные виды являются единственными видами в своих. xib-файлах. Таким образом, многоразовый сборный вид становится корневым видом вашего. xib-файла. Именно так создаются колонтитулы в сборных видах.
3. Если вы хотите более полно контролировать поведение. xib-файлов, создайте класс Objective-C и ассоциируйте с ним корневой вид вашего. xib-файла. Таким образом, всякий раз, когда iOS будет загружать с диска содержимое. xib-файла, ассоциированный с ним класс также будет загружаться в память и вы будете получать доступ к иерархии видов в. xib-файле.
4. Инстанцируйте метод экземпляра registerNib: forSupplementaryViewOfKind: withReuseIdentifier: сборного вида и зарегистрируйте ваши nib-файлы для разновидностей видов UICollectionElementKindSectionHeader и UICollectionElementKindSectionFooter.
5. Чтобы правильно оформить виды верхних и нижних колонтитулов перед тем, как они будут отображены, реализуйте метод collectionView: viewForSupplementaryElementOfKind: atIndexPath: источника данных сборного вида, а в этом методе запустите другой метод сборного вида, dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:, чтобы извлечь из очереди многоразовый вид верхнего или нижнего колонтитула.
6. Наконец, необходимо убедиться, что размер видов для верхних и нижних колонтитулов задается путем присваивания соответствующих значений свойствам headerReferenceSize и footerReferenceSize макетного объекта, отвечающего за последовательную компоновку.
Обсуждение
Итак, теперь нам требуется создать. xib-файлы для специальных верхних и нижних колонтитулов. Назовем их Header.xib и Footer.xib. Мы создаем их по тому же принципу, который описан в разделе 5.5, поэтому я не буду вновь повторять здесь этот материал. Убедитесь в том, что и для верхнего и для нижнего колонтитула у вас есть по одному классу Objective-C. Назовите их соответственно Header и Footer. Необходимо гарантировать, что оба этих класса наследуют от UICollectionReusableView. Закончив с этим, сконфигурируйте в конструкторе интерфейса подпись и кнопку, затем перетащите подпись в файл Header, а кнопку — в файл Footer. Свяжите их с вашими классами так, как показано на рис. 5.10 и 5.11.
Рис. 5.10. Конфигурирование ячейки верхнего колонтитула для сборного вида в конструкторе интерфейса
Рис. 5.11. Конфигурирование ячейки нижнего колонтитула для сборного вида в конструкторе интерфейса
Я связал подпись из верхнего колонтитула с классом Header с помощью свойства аутлета[4] в файле Header.h. Назовем аутлет просто label:
#import <UIKit/UIKit.h>
@interface Header: UICollectionReusableView
@property (weak, nonatomic) IBOutlet UILabel *label;
@end
То же самое я делаю и в нижнем колонтитуле, связав кнопку из файла Footer.xib с аутлетом из файла Footer.h и назвав аутлет button:
#import <UIKit/UIKit.h>
@interface Footer: UICollectionReusableView
@property (weak, nonatomic) IBOutlet UIButton *button;
@end
Теперь в контроллере сборного вида определим идентификаторы для ячеек верхнего и нижнего колонтитулов:
#import «ViewController.h»
#import «MyCollectionViewCell.h»
#import «Header.h»
#import «Footer.h»
static NSString *kCollectionViewCellIdentifier = @"Cells";
static NSString *kCollectionViewHeaderIdentifier = @"Headers";
static NSString *kCollectionViewFooterIdentifier = @"Footers";
@implementation ViewController
…
Далее в методе-инициализаторе сборного вида зарегистрируем ячейку сборного вида, ячейку верхнего колонтитула и ячейку нижнего колонтитула. Для этого воспользуемся nib-файлами, которые мы загружаем в память:
— (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];
/* Регистрируем nib-файл верхнего колонтитула */
UINib *headerNib = [UINib
nibWithNibName: NSStringFromClass([Header class])
bundle: [NSBundle mainBundle]];
[self.collectionView registerNib: headerNib
forSupplementaryViewOfKind: UICollectionElementKindSectionHeader
withReuseIdentifier: kCollectionViewHeaderIdentifier];
/* Регистрируем nib-файл нижнего колонтитула */
UINib *footerNib = [UINib
nibWithNibName: NSStringFromClass([Footer class])
bundle: [NSBundle mainBundle]];
[self.collectionView registerNib: footerNib
forSupplementaryViewOfKind: UICollectionElementKindSectionFooter
withReuseIdentifier: kCollectionViewFooterIdentifier];
}
return self;
}
Переходим к реализации метода collectionView: viewForSupplemen taryElementOfKind: atIndexPath: сборного вида. Этот метод нужен нам для конфигурирования верхних и нижних колонтитулов и предоставления их обратно сборному виду:
— (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath{
NSString *reuseIdentifier = kCollectionViewHeaderIdentifier;
if ([kind isEqualToString: UICollectionElementKindSectionFooter]){
reuseIdentifier = kCollectionViewFooterIdentifier;
}
UICollectionReusableView *view =
[collectionView dequeueReusableSupplementaryViewOfKind: kind
withReuseIdentifier: reuseIdentifier
forIndexPath: indexPath];
if ([kind isEqualToString: UICollectionElementKindSectionHeader]){
Header *header = (Header *)view;
header.label.text = [NSString stringWithFormat:@"Section Header %lu",
(unsigned long)indexPath.section + 1];
}
else if ([kind isEqualToString: UICollectionElementKindSectionFooter]){
Footer *footer = (Footer *)view;
NSString *h2 = [NSString stringWithFormat:@"Section Footer %lu",
(unsigned long)indexPath.section + 1];
[footer.button setTitle: h2 forState: UIControlStateNormal];
}
return view;
}
Наконец, необходимо убедиться, что макету с последовательной компоновкой известно о том, что в сборном виде есть ячейки верхнего и нижнего колонтитулов. На основе кода, написанного в разделе 5.3, изменим метод flowLayout делегата нашего приложения следующим образом:
— (UICollectionViewFlowLayout *) flowLayout{
UICollectionViewFlowLayout *flowLayout =
[[UICollectionViewFlowLayout alloc] init];
flowLayout.minimumLineSpacing = 20.0f;
flowLayout.minimumInteritemSpacing = 10.0f;
flowLayout.itemSize = CGSizeMake(80.0f, 120.0f);
flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
flowLayout.sectionInset = UIEdgeInsetsMake(10.0f, 20.0f, 10.0f, 20.0f);
/* Задаем базовый размер для видов с верхними и нижними колонтитулами */
flowLayout.headerReferenceSize = CGSizeMake(300.0f, 50.0f);
flowLayout.footerReferenceSize = CGSizeMake(300.0f, 50.0f);
return flowLayout;
}
Итак, все готово! Если вы теперь запустите приложение в эмуляторе iPad, то увидите примерно такой результат, как на рис. 5.12.
Рис. 5.12. Верхние и нижние колонтитулы в сборном виде
См. также
Разделы 5.2, 5.3, 5.5.
5.8. Добавление собственных вариантов взаимодействий к сборным видам
Постановка задачи
Вы хотели бы добавить к сборному виду собственные механизмы распознавания жестов, таких как щипок, чтобы реализовать собственные варианты поведений на базе уже имеющихся.
Решение
Инстанцируйте механизм распознавания жестов, а потом просмотрите распознаватели жестов, уже имеющиеся в сборном виде, и проверьте, нет ли среди них такого, который похож на нужный вам. Если найдете такой механизм, вызовите в нем метод requireGestureRecognizerToFail: и передайте этому методу в качестве параметра ваш собственный распознаватель жестов. Так вы гарантируете, что распознаватель жестов, имеющийся в сборном виде и напоминающий тот, что нужен вам, будет в случае необходимости подхватывать обработку жестов. Это станет происходить, если вашему распознавателю жестов не удастся обработать те или иные данные либо данные не будут соответствовать его требованиям/критериям. Таким образом, если ваш распознаватель способен обработать жест, он это сделает, в противном случае жест будет передан механизму распознавания, уже имеющемуся в сборном виде. Этот механизм обработает его сам.
Как только выполните описанную работу, добавьте ваш распознаватель жестов к сборному виду. Как вы помните, в экземпляре UICollectionViewController объект вашего сборного вида будет доступен через свойство контроллера collectionView, а не через свойство view.
Обсуждение
В API iOS уже предусмотрено несколько механизмов обработки жестов, применяемых в сборных видах. Поэтому для добавления собственных распознавателей жестов к уже имеющейся коллекции сначала нужно убедиться в том, что ваши распознаватели жестов не будут функционально пересекаться с уже имеющимися. Для этого сначала нужно инстанцировать ваши собственные распознаватели жестов, а потом, как описано ранее, просмотреть массив таких распознавателей, уже имеющихся у сборного вида. Затем понадобится вызвать метод requireGestureRecognizerToFail: в классе распознавателя жестов того же типа, что и наш распознаватель жестов, который мы собираемся добавить к сборному виду.
Рассмотрим пример. В этом примере мы собираемся добавить к сборному виду возможность уменьшения и увеличения изображения (то есть его масштабирования). Этот пример будет выстроен на основе того, который мы подготовили в разделе 5.5. Итак, первым делом мы должны добавить распознаватель щипков в коллекцию распознавателей жестов, имеющихся в сборном виде. Мы сделаем это в методе viewDidLoad контроллера сборного вида:
— (void) viewDidLoad{
[super viewDidLoad];
self.collectionView.backgroundColor = [UIColor whiteColor];
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]
initWithTarget: self
action:@selector(handlePinches:)];
for (UIGestureRecognizer *recognizer in
self.collectionView.gestureRecognizers){
if ([recognizer isKindOfClass: [pinch class]]){
[recognizer requireGestureRecognizerToFail: pinch];
}
}
[self.collectionView addGestureRecognizer: pinch];
}
Настраиваем распознаватель жестов щипка для вызова метода handlePinches: контроллера вида. Сейчас мы напишем этот метод:
— (void) handlePinches:(UIPinchGestureRecognizer *)paramSender{
CGSize DefaultLayoutItemSize = CGSizeMake(80.0f, 120.0f);
UICollectionViewFlowLayout *layout =
(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
layout.itemSize =
CGSizeMake(DefaultLayoutItemSize.width * paramSender.scale,
DefaultLayoutItemSize.height * paramSender.scale);
[layout invalidateLayout];
}
В этом коде есть две очень важные детали.
1. Предполагается, что по умолчанию размер элемента в макете последовательной компоновки сборного вида имеет ширину 80 точек и высоту 120 точек. Именно так мы создали макет с последовательной компоновкой для сборного вида в разделе 5.3. Затем мы берем коэффициент масштабирования, полученный от распознавателя жестов щипка, и умножаем на него размер элементов из сборного вида. В результате эти экранные элементы могут уменьшиться или увеличиться в зависимости от того, как именно пользователь масштабирует экран.
2. После того как был изменен размер элемента, применяемый по умолчанию в макете с последовательной компоновкой, макет необходимо обновить. В табличных видах мы обновляли либо нужные секции таблицы, либо всю таблицу, но в данном случае обновляем или упраздняем макет, прикрепленный к сборному виду. Это делается, чтобы сборный вид полностью «перерисовал» себя после изменения макета. Поскольку сборный вид в каждый момент времени может содержать всего один макетный объект, при упразднении такого макетного объекта потребуется перезагрузить весь сборный вид. Если бы мы могли иметь отдельный макет для каждой секции, то могли бы перезагружать только те секции, которые связаны с данным макетом. Но, имея такой код, как сейчас, при упразднении макетного объекта придется перерисовывать весь сборный вид.
Теперь, запустив код, вы заметите, что можете взаимодействовать с экраном с помощью двух пальцев. Если вы сводите пальцы, то элементы вашего сборного вида увеличиваются в размере, а если разводите — уменьшаются.
См. также
Разделы 5.3 и 5.5.
5.9. Представление контекстных меню в ячейках сборных видов
Постановка задачи
Если пользователь нажимает на один из экранных элементов в вашем сборном виде и удерживает на нем палец, требуется вывести контекстное меню. С помощью команд из этого меню элемент можно будет скопировать, переместить и т. д.
Решение
Контекстные меню по умолчанию встроены в сборные виды. Чтобы активизировать их, потребуется всего лишь реализовать следующие методы из протокола UICollectionViewDelegate.
• collectionView: shouldShowMenuForItemAtIndexPath: — среда времени исполнения передает этому методу индексный путь к элементу. Метод возвращает логическое значение, указывающее дальнейшее действие: должен этот элемент открывать контекстное меню или нет.
• collectionView: canPerformAction: forItemAtIndexPath: withSender: — среда времени исполнения передает этому методу селектор типа SEL. Можно проверить селектор (обычно для этого он преобразуется в строку, которая затем сравнивается со строкой, представляющей действие) и определить, хотите ли вы, чтобы указанное действие произошло. Возвратите YES, чтобы разрешить такое действие, либо NO, чтобы подавить его. Не забывайте, что вы всегда можете преобразовать селектор в строку, воспользовавшись методом NSStringFromSelector. Типичные примеры селекторов — copy: или paste: для команд контекстного меню Копировать или Вставить соответственно.
• collectionView: performAction: forItemAtIndexPath: withSender: — здесь выполняется действие, которое было с вашего разрешения отображено в сборном виде с помощью вышеупомянутых делегатных методов.
Обсуждение
Не откладывая в долгий ящик, расширим код, написанный в разделе 5.5. Мы будем выводить в ячейках контекстное меню Copy (Копировать), если пользователь нажмет на ячейку и на некоторое время задержит на ней палец. Когда пользователь выбирает элемент в меню копирования, мы скопируем изображение из ячейки в буфер обмена. После этого пользователь сможет вставить это изображение в файлы из других программ, например из почтового приложения Mail.
Первым делом реализуем в делегате сборного вида метод collectionView: shouldShowMenuForItemAtIndexPath:. В данном примере мы работаем с контроллером сборного вида, который сам является и делегатом и источником данных. Поэтому фактически нам придется всего лишь реализовать вышеупомянутый метод в контроллере сборного вида, вот так: