Текст книги "iOS. Приемы программирования"
Автор книги: Вандад Нахавандипур
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 23 (всего у книги 59 страниц)
Разделы 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:. В данном примере мы работаем с контроллером сборного вида, который сам является и делегатом и источником данных. Поэтому фактически нам придется всего лишь реализовать вышеупомянутый метод в контроллере сборного вида, вот так:
– (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.imageViewBackgroundImage.image];
}
}
Теперь, если вы запустите приложение и нажмете один из элементов в сборном виде, а потом будете удерживать на нем палец, то получите примерно такой результат, как на рис. 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]5
Интересная статья о сегвеях и их роли в раскадровках: http://www.raywenderlich.com/ru/24949/. – Примеч. пер.
[Закрыть]. Сегвей – это экземпляр класса 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.title = @"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.title = 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 наилучшим выходом из ситуации.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.