iOS. Приемы программирования Нахавандипур Вандад
— (BOOL) collectionView:(UICollectionView *)collectionView
shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath{
return YES;
}
Теперь мы хотим обеспечить, чтобы в ячейках нашего сборного вида отображалось лишь контекстное меню для копирования. В рамках этого примера рассмотрим, как можно отфильтровать доступные элементы меню и отобразить только нужные:
— (BOOL) collectionView:(UICollectionView *)collectionView
canPerformAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender{
if (action == @selector(copy:)){
return YES;
}
return NO;
}
Как видите, нам даже не требуется преобразовывать селектор в строку, чтобы сравнить его с такими строками, как copy:. Мы всего лишь используем оператор равенства, чтобы проверить, отвечает ли запрошенный селектор нашим ожиданиям. Если это так, возвращаем YES, в противном случае — NO.
Наконец, нам потребуется реализовать в делегате метод collectionView: performAction: forItemAtIndexPath: withSender:. С помощью этого метода мы узнаем, было ли вызвано действие копирования, а потом копируем изображение, взятое из ячейки, в буфер обмена. Пользователь сможет вставить из буфера изображение в файл из совершенно другого приложения:
— (void) collectionView:(UICollectionView *)collectionView
performAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender{
if (action == @selector(copy:)){
MyCollectionViewCell *cell = (MyCollectionViewCell *)[collectionView
cellForItemAtIndexPath: indexPath];
[[UIPasteboard generalPasteboard]
setImage: cell.iViewBackgroundImage.i];
}
}
Теперь, если вы запустите приложение и нажмете один из элементов в сборном виде, а потом будете удерживать на нем палец, то получите примерно такой результат, как на рис. 5.13.
Рис. 5.13. Элемент контекстного меню, отображаемый в ячейке сборного вида
См. также
Раздел 5.5.
Глава 6. Раскадровки
6.0. Введение
Программисты iOS уже привыкли работать с контроллерами видов. Мы умеем пользоваться навигационными контроллерами, чтобы выводить на экран и убирать с него контроллеры видов. Но Apple полагает, что такие задачи можно решать и проще, и поэтому в системе появились раскадровки. Раскадровки — это новый способ определения связей между экранами вашего приложения. Например, если в вашем приложении 20 уникальных контроллеров видов, вы написали эти контроллеры год назад, а сейчас снова изучаете исходный код, то вам придется снова распутывать все замысловатые соединения между контроллерами видов. Вы будете пытаться запомнить, какой именно контроллер вида поднимается вверх по стеку, когда пользователь совершает то или иное действие. Это может быть очень сложно, особенно если вы не слишком подробно документировали код. И вот тут вам поможет раскадровка. Раскадровка позволяет просматривать или создавать сразу весь пользовательский интерфейс приложения, а также выстраивать связи между контроллерами видов на одном экране. Да, все настолько просто.
Чтобы воспользоваться преимуществами, которые дает раскадровка, необходимо вплотную познакомиться с конструктором интерфейсов. Не волнуйтесь: обо всем важном рассказано в этой главе.
При работе с раскадровками каждый экран, наполненный значимым содержимым, называется сценой. Отношение между сценой и раскадровкой в iPhone можно сравнить с отношением вида к контроллеру вида. Весь контент сцены отображается на экране одновременно, соответственно, и пользователь воспринимает эту информацию одновременно. На iPad пользователь одновременно может просматривать более одной сцены, так как у планшета довольно большой экран.
При раскадровке возможен переход от одной сцены к другой. Применяемый в раскадровке процесс, в ходе которого один контроллер вида ставится выше другого, называется сегвеем. Еще одним примером перехода является ситуация с модальным контроллером вида, который на время поднимает сцену «снизу» экрана так, чтобы она заполнила весь экран. На iPad модальные окна обычно появляются в центре экрана, в это время остальная часть экрана затемняется. Таким образом подчеркивается, что в момент отображения модального окна именно оно является основным каналом ввода.
6.1. Добавление в раскадровку навигационного контроллера
Постановка задачи
Требуется возможность управлять несколькими котроллерами видов в приложении, построенном на основе раскадровки.
Решение
Задайте навигационный контроллер как исходный контроллер вида в файле раскадровки.
Обсуждение
Если вы создали в Xcode новое универсальное приложение, воспользовавшись шаблоном Single View Application (Приложение с единственным видом), то у вас будет два файла раскадровок: Main_iPhone.storyboard и Main_iPad.storyboard. Если просмотреть их в конструкторе интерфейса, то легко заметить, что контроллер вида применяется в них в качестве корневого контроллера. На рис. 6.1 показано содержимое простого готового файла раскадровки для iPhone.
Рис. 6.1. Контроллер вида в качестве корневого элемента файла раскадровки
Чтобы заменить корневой контроллер вида в файле раскадровки на навигационный контроллер, выполните следующие шаги.
1. Выберите контроллер вида на холсте раскадровки.
2. В меню Edit (Правка) выберите команду Embed in (Встроить), а затем Navigation Controller (Навигационный контроллер) (рис. 6.2).
Рис. 6.2. Активизация контроллера вида в навигационном контроллере
Как только справитесь с этим, вы заметите, что контроллер вида в раскадровке превратился в навигационный контроллер (рис. 6.3).
Рис. 6.3. Навигационный контроллер теперь является корневым контроллером раскадровки
См. также
Раздел 6.0.
6.2. Передача данных с одного экрана на другой
Постановка задачи
Необходимо передавать данные из одной сцены в другую, используя раскадровку.
Решение
Воспользуйтесь сегвеями, обеспечивающими плавные переходы.
Обсуждение
Сегвей — это объект, напоминающий любые другие объекты языка Objective-C. Чтобы выполнить переход от одной сцены к другой, среда времени исполнения раскадровки создает объект-сегвей именно для этой цели[5]. Сегвей — это экземпляр класса UIStoryboardSegue. Чтобы начался переход, текущий контроллер вида (этот вид уходит с экрана по завершении плавного перехода) получает сообщение prepareForSegue: sender:, где в качестве параметра prepareForSegue будет использован объект типа UIStoryboardSegue. Если вы хотите передать какие-либо данные от актуального контроллера вида к контроллеру того вида, который вот-вот появится на экране, это нужно делать в методе prepareForSegue: sender:.
Для полноценной работы с этим разделом нужно выполнить инструкции из раздела 6.1, где в раскадровке создаются два контроллера видов внутри навигационного контроллера.
Рассмотрим прикладной пример с использованием сегвеев. В этом разделе мы собираемся отобразить на экране примерно такой контроллер вида, как показан на рис. 6.4.
Рис. 6.4. Первый контроллер вида в нашем приложении; на контроллере вида есть текстовое поле и кнопка
Та информация, которую пользователь внесет в текстовое поле, будет передана второму контроллеру вида посредством сегвея и задана в качестве заголовка этого контроллера вида. Холст второго контроллера вида будет пуст. Итак, воспользуйтесь приемами, изученными в разделе 6.1, и поместите первый контроллер вида в навигационный контроллер. Теперь возьмите в библиотеке объектов другой контроллер вида, поместите его в раскадровку, а также разместите в первом контроллере вида кнопку и текстовое поле. Вы заметите, что положение текстового поля и кнопки получается примерно таким, как на рис. 6.4, но такое сходство не является обязательным. Можете расположить элементы как хотите. Теперь, удерживая нажатой клавишу Ctrl, наведите указатель на экранную кнопку и нажмите и не отпускайте кнопку мыши. На экране появится линия. Перетащите ее на второй контроллер вида (рис. 6.5). Откроется диалоговое окно, в нем выберите элемент Push. Сделав это, вы устанавливаете связь между кнопкой и вторым контроллером вида. Когда кнопка нажимается, контроллер вида оказывается на верхней позиции в стеке навигационного контроллера.
Рис. 6.5. Создание связи между кнопкой и вторым контроллером вида; связь срабатывает при нажатии кнопки
В конструкторе интерфейса видно, что мы создали сегвей между первым и вторым контроллерами вида. Щелкните на сегвее в инспекторе атрибутов (Attribute Inspector), присвойте ему идентификатор pushSecondViewController (рис. 6.6).
Рис. 6.6. Присваивание идентификатора сегвею
Может возникнуть вопрос: а зачем вообще нужен этот идентификатор? Дело в том, что мы реализуем специальный метод контроллера вида, который сначала будет спрашивать, допустимо ли сейчас выполнить такой сегвей. В этом методе мы проверим текст, находящийся в текстовом поле, и, если это поле окажется пустым, не позволим пользователю перейти на следующий экран. Метод, который будет вызываться в контроллере вида, называется shouldPerformSegueWithIdentifier: sender:, он относится к классу UIViewController. Вы можете использовать значение типа NSString, записываемое в его параметр shouldPerformSegueWithIdentifier, чтобы получить идентификатор того сегвея, который собирается выполнить система. После этого вы будете должны возвратить значение YES, если планируемый сегвей вас устраивает, и NO — в противном случае. Если вернуть NO, то сегвей с заданным идентификатором выполнен не будет. Но блокировать переход, никак не дав знать об этом пользователю, — безусловно, порочная практика. Поэтому, если поле оказывается пустым, а пользователь пытается нажать кнопку и перейти на следующий экран, мы отобразим для него такое диалоговое окно, как на рис. 6.7.
Рис. 6.7. Пользователь не сможет перейти на следующий экран, пока не введет текст в следующее поле
Итак, наконец перейдем к реализации первого контроллера вида. Предполагаю, что вы уже соединили текстовое поле с контроллером вида (поле выступает в качестве аутлета для этого контроллера) и можете получить доступ к его свойству text, перед тем как произойдет сегвей:
#import «ViewController.h»
#import «SecondViewController.h»
@interface ViewController () <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UITextField *textField;
@end
@implementation ViewController
— (void) viewDidLoad{
[super viewDidLoad];
self.h2 = @"First Screen";
}
— (BOOL) textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
— (void) displayTextIsRequired{
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle: nil
message:@"Please enter some text in the text field"
delegate: nil
cancelButtonTitle: nil
otherButtonTitles:@"OK", nil];
[alert show];
}
— (BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier
sender:(id)sender{
/* Проверяем, есть ли в текстовом поле какой-либо текст. Если текста
там нет, то отображаем пользователю соответствующее сообщение
и не позволяем перейти на следующий экран */
if ([identifier isEqualToString:@"pushSecondViewController"]){
if ([self.textField.text length] == 0){
[self displayTextIsRequired];
return NO;
}
};
return YES;
}
— (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:@"pushSecondViewController"]){
SecondViewController *nextController =
segue.destinationViewController;
[nextController setText: self.textField.text];
}
}
@end
Метод prepareForSegue: sender: этого контроллера вида вызывает метод экземпляра setText:, относящийся к классу SecondViewController. Как понятно из названия, это класс второго контроллера вида. Мы просто реализуем этот метод следующим образом:
#import «SecondViewController.h»
@interface SecondViewController ()
@end
@implementation SecondViewController
— (void) setText:(NSString *)paramText{
self.h2 = paramText;
}
@end
Вот и все. Теперь, если вы запустите предложение и введете в текстовое поле текст, скажем, Hello, World! а потом нажмете кнопку, то увидите примерно такую картинку, как на рис. 6.8.
Рис. 6.8. Наш текст успешно отобразился в качестве заголовка второго контроллера вида
См. также
Раздел 6.1.
6.3. Добавление в раскадровку контроллера с панелью вкладок
Постановка задачи
С помощью раскадровок требуется создать приложение, построенное на базе контроллера с панелью вкладок.
Решение
Создайте в Xcode приложение с единственным видом и встройте первый контроллер вида в контроллер вида с панелью вкладок. Чтобы сделать это, выполните следующие шаги.
1. В конструкторе интерфейса выберите в раскадровке контроллер вида. В меню Editor (Редактор) выберите пункт Embed in (Встроить), а затем выберите Tab Bar Controller (Контроллер с панелью вкладок) (рис. 6.9).
Рис. 6.9. Встраивание корневого контроллера вида в контроллер с панелью вкладок
2. Перейдите в библиотеку объектов контроллера интерфейсов, найдите там новый экземпляр контроллера вида и перетащите его в раскадровку.
3. Удерживая нажатой клавишу Ctrl, перетащите указатель мыши из контроллера с вкладками в созданный вами контроллер вида (рис. 6.10). Откроется диалоговое окно, в котором следует найти раздел Relationship Segues (Взаимосвязи через сегвеи) и выбрать в нем View Controller (Контроллер вида) (рис. 6.11).
Рис. 6.10. Соединение контроллера вида с контроллером, имеющим панель вкладок
Рис. 6.11. Ассоциирование контроллера вида с массивом контроллеров видов, находящихся в контроллере с панелью вкладок
Теперь запустите приложение в эмуляторе iOS. В нижней части экрана вы увидите два элемента (рис. 6.12). Каждый из этих элементов соответствует одному из ваших контроллеров вида. Если бы это контроллерное приложение с вкладками создавал я, то я поместил бы в каждой из вкладок по навигационному контроллеру. Если вы также хотите задействовать такую возможность, воспользуйтесь приемами, изученными в разделе 6.1, и встройте контроллеры видов в навигационные контроллеры (рис. 6.13).
Рис. 6.12. Контроллеры вида успешно отображаются в контроллере с панелью вкладок
Рис. 6.13. Встраивание ваших контроллеров видов в навигационные контроллеры, расположенные в контроллере панели вкладок
См. также
Раздел 6.1.
6.4. Внедрение специальных переходов между сегвеями в раскадровке
Постановка задачи
Требуется внедрить и использовать в файлах раскадровки новый тип перехода, чтобы перемещение из одного контроллера вида в другой выполнялось специально заданным образом — например, с применением пользовательской анимации.
Решение
Создайте подкласс от UIStoryboardSegue и переопределите его метод perform в соответствии со стоящими перед вами задачами.
Обсуждение
По умолчанию в раскадровках предлагается несколько полезных типов сегвеев, например пуш-сегвеи и модальные сегвеи. Они, конечно, очень удобны, но иногда требуется выполнять переход от одного вида к другому каким-то особым образом. В таких случаях лучше всего воспользоваться специальным сегвеем. Итак, создадим сегвей. В первом контроллере вида мы разрешаем переход во второй контроллер вида, причем на экране этот переход будет выглядеть как перелистывание. Для решения этой задачи выполните следующие шаги.
1. Создайте в Xcode проект, основанный на шаблоне Single View Application (Приложение с единственным видом).
2. В файле раскадровки создайте второй контроллер вида и поместите кнопку в центр первого контроллера вида. Удерживая нажатой клавишу Ctrl, перетащите указатель с экранной кнопки на второй контроллер вида. На этом этапе на экране откроется диалоговое окно, где система запросит информацию о типе перехода, который вы хотите ассоциировать с данным сегвеем. В этом диалоговом окне выберите вариант Custom (Специальный) (рис. 6.14).
Рис. 6.14. Ассоциирование специального сегвея с действием, выполняемым кнопкой
3. Теперь выберите желаемый сегвей и в инспекторе атрибутов (Attribute Inspector) конструктора интерфейсов измените имя класса сегвея на MySegue (рис. 6.15). Этого класса пока не существует, но не волнуйтесь — мы напишем его уже в данном разделе.
Рис. 6.15. Присваивание сегвею нашего собственного имени класса
4. Теперь создайте в Xcode новый класс Objective-C внутри вашего проекта, назовите этот класс MySegue (именно такое имя вы присвоили ему на предыдущем этапе) и убедитесь в том, что этот класс наследует от UIStoryboardSegue. Как только система создаст для вас этот класс, реализуйте его метод perform следующим образом:
#import «MySegue.h»
@implementation MySegue
— (void) perform{
UIViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
[UIView transitionFromView: source.view
toView: destination.view
duration:0.50f
options: UIViewAnimationOptionTransitionFlipFromTop
completion: ^(BOOL finished) {
NSLog(@"Transitioning is finished");
}];
}
@end
Вот и все. Теперь можете запустить приложение и убедиться в том, что при нажатии кнопки в первом контроллере вида запустится специальный сегвей, который, в свою очередь, отобразит на экране второй контроллер вида. В этом примере мы используем для реализации перехода метод класса transitionFromView: toView: duration: options: completion:, относящийся к классу UIView. Этот метод принимает довольно много параметров, которые описаны далее:
• transitionFromView — вид, с которого должен начинаться переход. В контексте нашего сегвея это вид с исходным контроллером вида;
• toView — это целевой вид, к которому должен привести переход. В нашем сегвее это вид с целевым контроллером вида;
• duration — длительность анимации в секундах;
• options — тип анимации, которую вы хотите выполнить. Это значение типа UIViewAnimationOptions. Если вы хотите просмотреть все параметры, доступные здесь, нажмите на клавиатуре Command+Shift+O, введите UIViewAnimationOptions, а потом нажмите клавишу Enter;
• completion — блок завершения, вызываемый сразу же по окончании перехода.
Прежде чем завершить этот раздел, необходимо упомянуть еще об одной важной вещи. Работа, которую вы выполняете (специальный переход), должна осуществляться в методе экземпляра perform специального класса сегвея. Это, в частности, означает, что вы не можете вывести в этом методе окно с предупреждением и отобразить его для пользователя (а также рассчитывать на то, что пользователь сможет нажать кнопку Yes или No в зависимости от своего решения и только потом выполнить переход). Это не сработает. Поэтому сначала подумайте, чего вы хотите добиться в вашем сегвее и является ли создание подкласса от UIStoryboardSegue наилучшим выходом из ситуации.
См. также
Разделы 6.0 и 6.1.
6.5. Размещение изображений и других компонентов пользовательского интерфейса в раскадровках
Постановка задачи
Требуется размещать в файлах раскадровок изображения, кнопки и другие компоненты пользовательского интерфейса.
Решение
Воспользуйтесь библиотекой объектов из конструктора интерфейса и найдите в ней разные компоненты для пользовательского интерфейса. Когда будете готовы поместить их в файлы раскадровок, просто перетащите туда эти элементы. Затем вы сможете сконфигурировать эти компоненты в инспекторе атрибутов.
Обсуждение
Допустим, вы хотите разместить в раскадровке несколько изображений. Находясь в конструкторе интерфейса, где уже должен быть открыт файл раскадровки, нажмите комбинацию клавиш Ctrl+Alt+Command+3 и таким образом перейдите в библиотеку объектов. Здесь найдите компонент Image View (Вид с изображением) (рис. 6.16) и перетащите его в основной вид с контроллером. Теперь одновременно нажмите на клавиатуре Alt+Command+4, чтобы открыть инспектор атрибутов. На этой панели вы сможете сконфигурировать вид с изображением. Чтобы поместить изображение в этот вид, просто добавьте изображение к вашему проекту. Пока вид с изображением остается выделенным, в инспекторе атрибутов задайте для этого вида свойство i (рис. 6.17).
Рис. 6.16. Вид с изображением, служащий компонентом пользовательского интерфейса (так он выглядит в библиотеке объектов)
Рис. 6.17. Установка свойства-изображения для вида с изображением в инспекторе атрибутов в конструкторе интерфейса
Периодически могут возникать ситуации, когда вы просто не можете найти в библиотеке объектов нужный компонент, но уверены, что он существует. Со мной это тоже случалось. В библиотеке объектов есть очень удобная строка для поиска, в которой вы можете написать имя интересующего вас компонента. Чтобы попасть в строку поиска, убедитесь, что вы уже открыли библиотеку объектов (для этого требуется нажать сочетание клавиш Ctrl+Alt+Command+3), а затем Command+Alt+L (рис. 6.18).
Рис. 6.18. Строка поиска в библиотеке объектов позволяет быстро найти интересующий объект
В инспекторе атрибутов можно сконфигурировать большинство важнейших свойств различных компонентов пользовательского интерфейса, которые вы помещаете в раскадровки. Тем не менее для решения определенных задач не обойтись без написания кода.
См. также
Раздел 6.0.
Глава 7. Параллелизм
7.0. Введение
Параллелизм (конкурентное исполнение программ) возникает, когда одновременно выполняется несколько задач. Современные операционные системы позволяют параллельно выполнять задачи даже на одном процессоре. Такая возможность гарантируется, если выделить на каждую задачу строго определенный промежуток (квант) процессорного времени. Например, если за секунду требуется решить 10 задач и все они имеют одинаковый приоритет, то операционная система разделит 1000 мс на 10 и уделит решению каждой задачи 100 мс процессорного времени. Таким образом, все эти задачи решаются в течение одной секунды, и пользователю кажется, что это происходит параллельно.
При этом технологии развиваются, и теперь в нашем распоряжении есть процессоры с несколькими ядрами. Это означает, что один процессор действительно может решать несколько задач одновременно. Операционная система диспетчеризует задачи к процессору и дожидается, пока они будут выполнены. Вот так все просто!
Grand Central Dispatch (GCD) — это низкоуровневый API, написанный на языке С и работающий с блоковыми объектами. GCD отлично приспособлен для направления различных задач нескольким ядрам, так что программист может не задумываться о том, какое ядро решает какую задачу. Многоядерные устройства с операционной системой Mac OS X, в частности ноутбуки, имеются в свободном доступе уже довольно давно. А с появлением таких многоядерных устройств, как новый iPad, мы можем писать и интересные многопоточные приложения для системы iOS, рассчитанные на работу с несколькими ядрами.
Центральной составляющей GCD являются диспетчерские очереди. Диспетчерские очереди, как мы вскоре увидим, представляют собой пулы потоков, управляемые GCD в базовой операционной системе, будь то iOS или Mac OS X. Вы не будете работать с этими потоками напрямую. Вы будете иметь дело только с диспетчерскими очередями, распределяя задачи по этим очередям и приказывая очередям инициировать решение задач. GCD предлагает несколько режимов решения задач: синхронно, асинхронно, с определенной задержкой и т. д.
Чтобы приступить к использованию GCD в ваших приложениях, в проект не требуется импортировать каких-либо специальных библиотек. Apple уже встроила GCD в различные фреймворки, в частности в Core Foundation и Cocoa/Cocoa Touch. Все методы и типы данных, имеющиеся в GCD, начинаются с ключевого слова dispatch_. Например, dispatch_async позволяет направить задачу в очередь для асинхронного выполнения, а dispatch_after — выполнить блок кода после определенной задержки.