iOS. Приемы программирования Нахавандипур Вандад
Итак, продолжим и создадим в 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
};
См. также
Разделы 1.9 и 1.15.
Глава 2. Создание динамических и интерактивных пользовательских интерфейсов
2.0. Введение
Когда iPhone только появился на рынке, он поистине задал стандарт интерактивности в мобильных приложениях. Приложения iOS были и остаются поразительно интерактивными — вы можете на ходу манипулировать различными компонентами пользовательского интерфейса, корректируя их для максимально полного удовлетворения своих потребностей.
В iOS 7 Apple добавила в iOS SDK ряд новых классов, которые позволяют обогащать ваше приложение очень интересной физикой, делая их еще более интерактивными. Например, в новой iOS вы заметите, что фоновые рисунки, которые могут служить обоями Рабочего стола, стали еще живее, так как они могут двигаться по экрану, если вы качаете устройство влево-вправо, и т. д. Появились также новые разновидности поведений, которые iOS позволяет добавлять в приложения.
Приведу другой пример. Допустим, у вас есть приложение для обмена фотографиями, работающее на iPad. В левой части экрана находятся несколько картинок, которые были извлечены из пользовательского фотоальбома на Рабочий стол. Справа расположен компонент, напоминающий корзину. Каждая фотография, перенесенная в корзину, будет доступна для пакетного совместного использования через какую-нибудь социальную сеть, например Facebook. Вы хотите обогатить интерактивность приложения с помощью анимации так, чтобы пользователь мог «кидать» фотографии в корзину слева, а фотографии закреплялись в корзине. Все это можно было сделать и раньше, но для выполнения таких операций требовались глубокие знания Core Animation, а также довольно хорошее понимание физики.
Пользуясь UI Dynamics — новой технологией от Apple, — вы можете значительно упростить реализацию многих таких возможностей в приложении. На самом деле достаточно всего нескольких строк кода, чтобы реализовать в ваших видах очень интересную физику и необычное поведение.
Apple категоризировала такие действия в классах поведений, которые можно прикреплять к аниматору. Например, вы можете добавить к кнопке в вашем виде тяготение. В таком случае кнопка будет падать из верхней части вида (если вы ее там поместили) до самого его низа и даже сможет выпадать за его пределы. Если вы хотите этому воспрепятствовать и сделать так, чтобы кнопка падала только до дна вида и не дальше, то к аниматору нужно прикрепить и другое поведение — столкновение. Аниматор будет управлять поведениями, которые вы добавите к разным видам вашего приложения, а также их взаимодействиями. Вам не придется об этом беспокоиться. Далее перечислены несколько классов, обеспечивающих различное поведение компонентов пользовательского интерфейса:
• UICollisionBehavior — обеспечивает обнаружение столкновений;
• UIGravityBehavior — как понятно из названия, обеспечивает имитацию тяготения;
• UIPushBehavior — позволяет имитировать в ваших видах толчки. Допустим, вы дотронулись пальцем до экрана, а потом стали постепенно двигать палец к его верхнему краю. Если к виду прикреплена кнопка, оснащенная толчковым поведением, то вы могли бы толкать эту кнопку пальцем, как если бы она лежала на столе;
• UISnapBehavior — обеспечивает прикрепление видов к тем или иным местам на экране.
Как было указано ранее, для каждого динамического поведения потребуется аниматор типа UIDynamicAnimator. Аниматор должен быть инициализирован с сущностью, которая в Apple называется опорным видом. Аниматор использует систему координат опорного вида для расчета результата различных поведений. Например, можно указать вид определенного контроллера вида в качестве опорного для динамического аниматора. В таком случае можно добавить к аниматору поведение столкновения и тем самым гарантировать, что добавляемые элементы не будут выпадать за пределы опорного вида. Таким образом вы сможете разместить в опорном виде все элементы пользовательского интерфейса, даже если на них действует поведение тяготения.
Опорный вид используется также в качестве контекста для анимации, которой управляет аниматор. Например, аниматору требуется определить, столкнутся ли два квадрата друг с другом. Для этого он использует методы Core Graphics, позволяющие определить, будут ли два этих квадрата накладываться друг на друга в контексте их вышестоящего вида — в данном случае опорного вида.
В этой главе мы исследуем различные комбинации подобных поведений и поговорим о том, как вы сможете сделать свои приложения более интерактивными, вооружившись поведениями UIKit и аниматорами. Начнем с простых примеров и постепенно будем выстраивать на их основе изучаемый материал, знакомясь с все более захватывающими примерами.
2.1. Добавление тяготения к компонентам пользовательского интерфейса
Постановка задачи
Необходимо, чтобы компоненты вашего пользовательского интерфейса двигались так, как будто на них действует сила тяжести: например, если перетащить элемент к верхнему краю экрана, то под действием силы тяжести он упадет к нижнему краю. Объединив эту возможность с поведением столкновения, которое мы изучим позднее, можно создавать такие компоненты пользовательского интерфейса, которые будут падать со своего действительного местоположения, пока не столкнутся с указанной вами линией.
Решение
Инициализируйте объект типа UIGravityBehavior и добавьте к нему те компоненты пользовательского интерфейса, которые должны испытывать тяготение к этому объекту. Сделав это, создайте экземпляр UIDynamicAnimator, добавьте к аниматору поведение тяготения, а всю остальную работу аниматор сделает за вас.
Обсуждение
В этом разделе мы создадим простой вид, представляющий собой раскрашенный квадрат, находящийся в одновидовом приложении. Этот вид мы поместим в центре экрана. Затем добавим к нему поведение тяготения и посмотрим, как он будет падать вниз, пока не скроется за пределами экрана.
Итак, определим аниматор и вид:
#import «ViewController.h»
@interface ViewController ()
@property (nonatomic, strong) UIView *squareView;
@property (nonatomic, strong) UIDynamicAnimator *animator;
@end
@implementation ViewController
<# Оставшаяся часть кода вашего контроллера вида находится здесь #>
Далее создадим небольшой вид, присвоим ему цвет и поместим в центре вида нашего контроллера. Так мы получим экземпляр класса UIGravityBehavior, воспользовавшись методом-инициализатором initWithItems:. Этот инициализатор принимает массив объектов, соответствующих протоколу UIDynamicItem. По умолчанию этому протоколу соответствуют все экземпляры UIView, поэтому, как только вид готов, можно идти дальше:
— (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear: animated];
/* Создаем маленький квадратный вид и добавляем его к self.view */
self.squareView = [[UIView alloc] initWithFrame:
CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
self.squareView.backgroundColor = [UIColor greenColor];
self.squareView.center = self.view.center;
[self.view addSubview: self.squareView];
/* Создаем аниматор и реализуем тяготение */
self.animator = [[UIDynamicAnimator alloc]
initWithReferenceView: self.view];
UIGravityBehavior *gravity = [[UIGravityBehavior alloc]
initWithItems:@[self.squareView]];
[self.animator addBehavior: gravity];
}