Текст книги "iOS. Приемы программирования"
Автор книги: Вандад Нахавандипур
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 12 (всего у книги 59 страниц)
1.28. Организация разбивки на страницы с помощью UIPageViewController
Постановка задачиНеобходимо создать приложение, работающее по принципу iBooks, где пользователь может листать страницы, как в настоящей книге. Таким образом мы собираемся обеспечить пользователю интуитивно понятную и реалистичную работу с программой.
РешениеВоспользуйтесь UIPageViewController.
ОбсуждениеВ среде разработки Xcode есть шаблон для создания контроллеров с постраничной организацией. Перед тем как изучать этот раздел и узнать, что же они собой представляют, стоит просто посмотреть, как они выглядят. Итак, выполните следующие шаги, чтобы в вашем приложении можно было использовать контроллеры видов с постраничной организацией.
Контроллеры видов с постраничной организацией работают как в iPhone, так и в iPad.
1. В Xcode перейдите в меню File (Файл) и выберите NewNew Project (Новый Новый проект).
2. Убедитесь, что в левой части окна New Project (Новый проект) выбрана операционная система iOS, а далее – команда Application (Приложение). Сделав это, укажите справа шаблон Page-Based Application (Приложение с постраничной организацией) (рис. 1.74) и нажмите Next (Далее).
Рис. 1.74. Создание в Xcode приложения с постраничной организацией
3. Теперь выберите имя продукта и убедитесь в том, что указанное вами семейство устройств (Device) является универсальным (Universal). Это необходимо сделать, поскольку, как правило, ваше приложение потребуется использовать и на iPhone, и на iPad (рис. 1.75). Сделав это, нажмите Next (Далее).
Рис. 1.75. Задаем настройки проекта для приложения с постраничной организацией
4. Выберите, где вы хотите сохранить проект. Сделав это, нажмите кнопку Create (Создать). Итак, вы успешно создали проект.
Теперь можете убедиться в том, что Xcode уже создала для вашего проекта несколько классов. Кратко рассмотрим каждый из них:
• класс делегата – делегат приложения просто создает экземпляр класса RootViewController и представляет его пользователю. Для iPad используется один архив XIB, для iPhone – другой, но оба они при работе опираются на вышеупомянутый класс;
• RootViewController – создает экземпляр UIPageViewController и добавляет к себе этот контроллер вида. Поэтому пользовательский интерфейс контроллера данного вида – это фактически смесь двух контроллеров видов, самого RootViewController и UIPageViewController;
• DataViewController – для каждой страницы в контроллере постраничного вида пользователю предлагается по одному экземпляру данного класса. Данный класс является подклассом UIViewController;
• ModelController – это обычный подкласс NSObject, соответствующий протоколу UIPageViewControllerDataSource. Этот класс является источником данных для контроллера вида-страницы.
Итак, мы видим, что у контроллера страничного вида есть и делегат, и источник данных. При использовании стандартного шаблона для приложений с постраничной организацией, входящего в состав Xcode, корневой контроллер вида становится делегатом, а контроллер модели – источником данных для контроллера страничного вида. Чтобы понять, как же на самом деле работает контроллер вида-страницы, необходимо разобраться в протоколах, регламентирующих в нем процессы делегирования и обращения к источнику данных. Начнем с протокола делегата, UIPageViewControllerDelegate. В этом протоколе есть два важных метода:
– (void)pageViewController:(UIPageViewController *)pageViewController
didFinishAnimating:(BOOL)finished
previousViewControllers:(NSArray *)previousViewControllers
transitionCompleted:(BOOL)completed;
– (UIPageViewControllerSpineLocation)pageViewController
:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation;
Первый метод вызывается, когда пользователь переходит к следующей или предыдущей странице или решает перелистнуть страницу вперед или назад, но передумывает в момент, пока страница еще движется. (В последнем случае пользователь возвращается к той странице, которую просматривал перед актом листания.) Свойство transitionCompleted получает значение YES, если удалось отобразить анимацию листания страницы, и NO – если пользователь решил страницу не перелистывать и прервал анимацию в ходе ее выполнения.
Второй метод вызывается при каждом изменении ориентации устройства. Этот метод можно использовать для того, чтобы указывать положение сгиба страницы, возвращая значение типа UIPageViewControllerSpineLocation:
typedef NS_ENUM(NSInteger, UIPageViewControllerSpineLocation) {
UIPageViewControllerSpineLocationNone = 0,
UIPageViewControllerSpineLocationMin = 1,
UIPageViewControllerSpineLocationMid = 2,
UIPageViewControllerSpineLocationMax = 3
};
Возможно, все это выглядит немного запутанно, но позвольте мне продемонстрировать, что имеется в виду. Если мы используем расположение сгиба ViewControllerSpineLocationMin, то для отображения страничного вида пользователю потребуется всего один контроллер вида. Если пользователь перейдет к следующей странице, то увидит уже новый контроллер вида. Но если мы зададим для отображения сгиба UIPageViewControllerSpineLocationMid, то для демонстрации такого варианта нам понадобятся уже два контроллера видов одновременно. Один будет представлять левую страницу, другой – правую, а между ними расположится сгиб. Сейчас покажу, что я имею в виду. На рис. 1.76 изображен пример страничного вида, имеющего альбомную ориентацию. Здесь для расположения изгиба выбрано значение UIPageViewControllerSpineLocationMin.
Рис. 1.76. Один контроллер вида. Представлен контроллер вида-страницы с альбомной ориентацией
Теперь, если вернуть расположение сгиба, соответствующее UIPageViewControllerSpineLocationMid, получим примерно такой результат, как на рис. 1.77.
Рис. 1.77. Два контроллера видов, отображенные в контроллере вида-страницы, где страница имеет альбомную ориентацию
Как видно на рис. 1.77, сгиб расположен точно по центру экрана, между двумя контроллерами видов. Когда пользователь перелистывает страницу справа налево, страница оказывается слева, а справа контроллер вида-страницы отображает новую страницу. Вся логика заключена в следующем методе делегата:
– (UIPageViewControllerSpineLocation)pageViewController
:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation;
Итак, мы разобрались с делегатом контроллера страничного вида, а что насчет источника данных? Источник данных контроллера страничного вида должен соответствовать протоколу UIPageViewControllerDataSource. Этот протокол предоставляет два следующих важных метода:
– (UIViewController *)
pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController;
– (UIViewController *)
pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController;
Первый метод вызывается, когда контроллер вида-страницы уже имеет на экране устройства один контроллер вида и должен узнать, какой из предыдущих контроллеров видов нужно отображать. Это происходит, когда пользователь решает перейти на следующую страницу (перелистнуть имеющуюся). Второй метод вызывается, когда контроллеру вида необходимо узнать, какой контроллер вида отобразить вслед за той страницей, которую пользователь перелистнет.
Как вы могли убедиться, среда Xcode значительно упрощает создание приложений с постраничной организацией. Все, что, по сути, от вас требуется, – предоставить содержимое для модели данных (ModelController) и двигаться дальше. Если требуется отдельно настроить цвета и изображения в контроллерах ваших видов, то можно либо сделать это в конструкторе интерфейса (Interface Builder), позволяющем напрямую изменять файлы раскадровки, либо написать собственный код для реализации каждого из контроллеров видов.
1.29. Отображение вспомогательных экранов с помощью UIPopoverController
Постановка задачиВы хотите отображать на iPad окно с информацией, не занимая при этом целый экран.
РешениеВоспользуйтесь вспомогательными экранами.
ОбсуждениеВспомогательные экраны (Popover) применяются для вывода на экран iPad дополнительной информации. В качестве примера можно привести браузер Safari из iPad. Если пользователь нажмет кнопку Bookmarks (Закладки), то на экране появится еще одно окошко, в котором будет перечислено содержимое панели закладок (рис. 1.78).
Рис. 1.78. Вспомогательный экран с закладками браузера Safari на планшете iPad
По умолчанию, если на устройстве отображен вспомогательный экран, а пользователь нажмет что-нибудь за его пределами, этот вспомогательный экран автоматически закроется. Вы можете задать поведение, при котором вспомогательный экран не закрывается, когда пользователь дотрагивается до какой-то конкретной части экрана, – об этом поговорим в дальнейшем. Содержимое вспомогательных экранов отображается с применением контроллеров видов. Обратите внимание: на вспомогательных экранах могут присутствовать и навигационные контроллеры, поскольку они являются подклассами UIViewController.
Вспомогательные экраны применяются только на устройствах iPad. Если у вас есть контроллер вида, чей код выполняется и на iPad, и на iPhone, необходимо гарантировать, что вспомогательные экраны не будут инстанцироваться на других устройствах, кроме iPad.
Вспомогательные экраны можно отображать и использовать двумя способами:
• открывать их из навигационной кнопки экземпляра UIBarButtonItem;
• открывать из прямоугольной области в виде.
При изменении ориентации (то есть при повороте) устройства вспомогательные экраны либо закрываются, либо временно скрываются. Следует позаботиться о том, чтобы пользователю было удобно работать с программой, вновь отобразив вспомогательный экран, когда устройство будет окончательно переориентировано, – если это возможно. В определенных случаях вспомогательный экран можно закрывать автоматически после того, как ориентация устройства изменится. Например, если пользователь нажимает какую-либо навигационную кнопку, когда устройство находится в альбомном режиме, вы можете отобразить вспомогательный экран. Предположим, что ваше приложение сделано так, что, когда ориентация изменяется на книжную, данная навигационная кнопка по какой-то причине удаляется с панели. Теперь, чтобы не путать пользователя, после изменения ориентации устройства на книжную нужно убрать и вспомогательный экран, ассоциированный с этой кнопкой. Но в некоторых случаях приходится немного импровизировать со вспомогательными экранами, чтобы пользователю было удобнее работать с программой, так как управление ориентацией устройства – более тонкий процесс, чем можно предположить из предыдущего сценария.
Для создания демонстрационного приложения со вспомогательными экранами нужно сначала обдумать стратегию, зависящую от поставленных перед вами требований. Например, требуется написать приложение с контроллером вида, загруженным в навигационный контроллер. В корневом контроллере вида будет отображаться кнопка +, расположенная в правом углу навигационной панели. Если на устройстве iPad нажать кнопку +, откроется вспомогательный экран с двумя кнопками. На одной будет написано Photo (Фото), на другой – Audio (Аудио). При нажатии той же самой навигационной кнопки на iPhone отобразится предупреждающий вид (Alert View) с тремя кнопками – двумя вышеупомянутыми и кнопкой Cancel (Отмена), чтобы пользователь мог сам закрыть это окно, если захочет. При нажатии любой из этих кнопок (и в предупреждающем виде iPhone, и на вспомогательном экране iPad) мы фактически ничего не сделаем – просто уберем это окошко.
Итак, продолжим и создадим в Xcode универсальный проект Single View (Приложение с единственным видом). Назовем проект Displaying Popovers with UIPopoverController («Отображение вспомогательных экранов с помощью UIPopoverController»). Затем, воспользовавшись приемами, описанными в разделе 6.1, добавим в раскадровку навигационный контроллер, чтобы у контроллеров видов появилась навигационная панель.
После этого перейдем к определению корневого контроллера вида и укажем здесь свойство типа UIPopoverController:
#import «ViewController.h»
@interface ViewController () <UIAlertViewDelegate>
@property (nonatomic, strong) UIPopoverController *myPopoverController;
@property (nonatomic, strong) UIBarButtonItem *barButtonAdd;
@end
@implementation ViewController
<# Оставшаяся часть вашего кода находится здесь #>
Как видите, мы также определяем для контроллера вида свойство barButtonAdd. Это навигационная кнопка, которую мы добавим на нашу панель. Мы собираемся отображать вспомогательный экран после того, как пользователь нажмет эту кнопку (подробнее о навигационных кнопках рассказано в разделе 1.15). При этом необходимо гарантировать, что мы инстанцируем вспомогательный экран только для iPad. Прежде чем идти дальше и инстанцировать корневой контроллер вида с навигационной кнопкой, создадим подкласс от UIViewController и назовем его PopoverContentViewController. В дальнейшем будем отображать его содержимое на вспомогательном экране. В разделе 1.9 подробнее рассказано о контроллерах видов и о том, как их создавать.
В контроллере информационного вида, отображаемого на вспомогательном экране, будет две кнопки (как мы и рассчитывали). Тем не менее в этом контроллере вида должна быть также ссылка на контроллер вспомогательного экрана. Она нужна, чтобы убрать вспомогательный экран, как только пользователь нажмет любую из кнопок. Сначала в контроллере информационного вида нужно определить специальное свойство для ссылки на вспомогательный экран:
#import <UIKit/UIKit.h>
@interface PopoverContentViewController: UIViewController
/* Не следует определять данное свойство как strong. В противном случае
возникнет цикл удержания (Retain Cycle) между контроллером
информационного вида и контроллером вспомогательного экрана,
так как контроллер вспомогательного экрана не даст исчезнуть
контроллеру информационного вида и наоборот. */
@property (nonatomic, weak) UIPopoverController *popoverController;
@end
И здесь же, в файле реализации контроллера вида с содержимым, объявим кнопки панели:
#import «PopoverContentViewController.h»
@interface PopoverContentViewController ()
@property (nonatomic, strong) UIButton *buttonPhoto;
@property (nonatomic, strong) UIButton *buttonAudio;
@end
@implementation PopoverContentViewController
<# Оставшаяся часть вашего кода находится здесь #>
Затем создадим две кнопки в контроллере информационного вида и свяжем их ссылками с методами, обеспечивающими их функционирование. Эти методы будут закрывать тот вспомогательный экран, в котором отображается контроллер информационного вида. Не забывайте, что контроллер вспомогательного экрана будет присваивать себя свойству popoverController, относящемуся к контроллеру информационного вида:
– (BOOL) isInPopover{
Class popoverClass = NSClassFromString(@"UIPopoverController");
if (popoverClass!= nil &&
UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad &&
self.popoverController!= nil){
return YES;
} else {
return NO;
}
}
– (void) gotoAppleWebsite:(id)paramSender{
if ([self isInPopover]){
/* Перейти на сайт и закрыть вспомогательный экран. */
[self.popoverController dismissPopoverAnimated: YES];
} else {
/* Обработать ситуацию с iPhone. */
}
}
– (void) gotoAppleStoreWebsite:(id)paramSender{
if ([self isInPopover]){
/* Перейти на сайт и закрыть вспомогательный экран. */
[self.popoverController dismissPopoverAnimated: YES];
} else {
/* Обработать ситуацию с iPhone. */
}
}
– (void)viewDidLoad{
[super viewDidLoad];
self.contentSizeForViewInPopover = CGSizeMake(200.0f, 125.0f);
CGRect buttonRect = CGRectMake(20.0f,
20.0f,
160.0f,
37.0f);
self.buttonPhoto = [UIButton buttonWithType: UIButtonTypeRoundedRect];
[self.buttonPhoto setTitle:@"Photo"
forState: UIControlStateNormal];
[self.buttonPhoto addTarget: self
action:@selector(gotoAppleWebsite:)
forControlEvents: UIControlEventTouchUpInside];
self.buttonPhoto.frame = buttonRect;
[self.view addSubview: self.buttonPhoto];
buttonRect.origin.y += 50.0f;
self.buttonAudio = [UIButton buttonWithType: UIButtonTypeRoundedRect];
[self.buttonAudio setTitle:@"Audio"
forState: UIControlStateNormal];
[self.buttonAudio addTarget: self
action:@selector(gotoAppleStoreWebsite:)
forControlEvents: UIControlEventTouchUpInside];
self.buttonAudio.frame = buttonRect;
[self.view addSubview: self.buttonAudio];
}
Теперь в методе viewDidLoad корневого контроллера вида создадим навигационную кнопку. В зависимости от типа устройства при нажатии навигационной кнопки мы будем отображать либо вспомогательный экран (на iPad), либо предупреждающий вид (на iPhone):
– (void)viewDidLoad{
[super viewDidLoad];
/* Проверяем, существует ли этот класс в том варианте iOS,
где действует приложение. */
Class popoverClass = NSClassFromString(@"UIPopoverController");
if (popoverClass!= nil &&
UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
PopoverContentViewController *content =
[[PopoverContentViewController alloc] initWithNibName: nil
bundle: nil];
self.popoverController = [[UIPopoverController alloc]
initWithContentViewController: content];
content.popoverController = self.popoverController;
self.barButtonAdd = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemAdd
target: self
action:@selector(performAddWithPopover:)];
} else {
self.barButtonAdd = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemAdd
target: self
action:@selector(performAddWithAlertView:)];
}
[self.navigationItem setRightBarButtonItem: self.barButtonAdd
animated: NO];
}
Контроллер вспомогательного экрана ставит на себя ссылку в контроллере информационного вида сразу после инициализации информационного вида. Это очень важно. Контроллер вспомогательного экрана невозможно инициализировать в отсутствие контроллера информационного вида. Как только контроллер вспомогательного экрана инициализирован посредством контроллера информационного вида, можно продолжать работу и изменять контроллер информационного вида в контроллере вспомогательного экрана – но этого нельзя делать в процессе инициализации.
Мы решили, что при нажатии навигационной кнопки + на устройстве iPad будет запускаться метод performAddWithPopover:. Если мы имеем дело не с iPad, то нужно, чтобы при нажатии этой кнопки запускался метод performAddWithAlertView:. Итак, реализуем два этих метода, а также позаботимся о методах делегатов предупреждающего вида – чтобы нам было известно, какую кнопку в предупреждающем виде нажимает пользователь, работающий с iPhone:
– (NSString *) photoButtonTitle{
return @"Photo";
}
– (NSString *) audioButtonTitle{
return @"Audio";
}
– (void) alertView:(UIAlertView *)alertView
didDismissWithButtonIndex:(NSInteger)buttonIndex{
NSString *buttonTitle = [alertView buttonTitleAtIndex: buttonIndex];
if ([buttonTitle isEqualToString: [self photoButtonTitle]]){
/* Добавляем фотографию… */
}
else if ([buttonTitle isEqualToString: [self audioButtonTitle]]){
/* Добавляем аудио… */
}
}
– (void) performAddWithAlertView:(id)paramSender{
[[[UIAlertView alloc] initWithTitle: nil
message:@"Add…"
delegate: self
cancelButtonTitle:@"Cancel"
otherButtonTitles:
[self photoButtonTitle],
[self audioButtonTitle], nil] show];
}
– (void) performAddWithPopover:(id)paramSender{
[self.popoverController
presentPopoverFromBarButtonItem: self.barButtonAdd
permittedArrowDirections: UIPopoverArrowDirectionAny
animated: YES];
}
Если запустить это приложение в эмуляторе iPad, то при нажатии кнопки + на навигационной панели мы увидим примерно такой интерфейс, как на рис. 1.79.
Рис. 1.79. Простой вспомогательный экран, отображаемый после нажатия навигационной кнопки
Если запустить это же универсальное приложение в эмуляторе iPhone и нажать на навигационной панели кнопку +, результат будет примерно как на рис. 1.80.
Рис. 1.80. В универсальном приложении вспомогательные экраны заменяются предупреждающими видами
Здесь мы воспользовались важным свойством контроллера информационного вида: preferredContentSize. Когда вспомогательный экран отображает контроллер своего информационного вида, он будет автоматически считывать значение этого свойства и корректировать свой размер (высоту и ширину). Кроме того, мы использовали метод presentPopoverFromBarButtonItem: permittedArrowDirections: animated: вспомогательного экрана в корневом контроллере нашего вида. Этот метод нужен, чтобы вспомогательный экран отображался над кнопкой навигационной панели. Первый параметр, принимаемый данным методом, – это кнопка навигационной панели, та, над которой должен всплывать контроллер вспомогательного экрана. Второй параметр указывает при появлении вспомогательного экрана направление его развертывания относительно объекта, из которого он появляется. Например, на рис. 1.79 видно, что стрелка вспомогательного экрана указывает вверх от кнопки с навигационной панели. Значение, передаваемое этому параметру, должно относиться к типу UIPopoverArrowDirection::
typedef NS_OPTIONS(NSUInteger, UIPopoverArrowDirection) {
UIPopoverArrowDirectionUp = 1UL << 0,
UIPopoverArrowDirectionDown = 1UL << 1,
UIPopoverArrowDirectionLeft = 1UL << 2,
UIPopoverArrowDirectionRight = 1UL << 3,
UIPopoverArrowDirectionAny = UIPopoverArrowDirectionUp |
UIPopoverArrowDirectionDown |
UIPopoverArrowDirectionLeft |
UIPopoverArrowDirectionRight,
UIPopoverArrowDirectionUnknown =
NSUIntegerMax
};
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.