• открывать их из навигационной кнопки экземпляра 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
   };

См. также

   Разделы 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];
 
   }
   Если вы не хотите добавлять тяготение ко всем вашим видам, как только инициализируете это поведение, то можете добавить его позже с помощью метода экземпляра addItem:, относящегося к классу UIGravityBehavior. Этот метод также принимает объект, соответствующий указанному ранее протоколу.
   Теперь запустите ваше приложение. Как только вид в контроллере появится на экране, вы увидите цветной квадрат, падающий из центра экрана вниз, до нижнего края, а потом скрывающийся за пределами дисплея. Поведение тяготения, точно как реальная сила тяжести, заставляет элементы двигаться вниз, вплоть до определенной границы. Поскольку в данном случае никакой границы нет, элемент падает вниз до бесконечности. Мы исправим этот недостаток позже в данной главе, реализовав для элементов поведение столкновения.

См. также

   Раздел 2.0.

2.2. Обнаружение столкновений между компонентами пользовательского интерфейса и реагирование на них

Постановка задачи

   Требуется задать на экране границы столкновений между компонентами вашего пользовательского интерфейса так, чтобы эти компоненты не перекрывали друг друга.

Решение

   Инстанцируйте объект типа UICollisionBehavior и прикрепите его к объекту аниматора. Присвойте свойству translatesReferenceBoundsIntoBoundary поведения столкновений значение YES и убедитесь в том, что аниматор инициализирован с вышестоящим видом в качестве опорной сущности. Так вы гарантируете, что дочерние виды, на которые распространяется поведение столкновения (о чем мы вскоре поговорим), не будут выпадать за пределы вышестоящего вида.

Обсуждение

   Поведение столкновения, относящееся к типу UICollisionBehavior, затрагивает объекты, соответствующие протоколу UIDynamicItem. Все виды типа UIView уже ему соответствуют, поэтому вам придется лишь инстанцировать ваши виды и добавить их к поведению столкновения. Поведение столкновения требует определить на экране границы, которые будут непреодолимы для элементов, находящихся в аниматоре. Например, если вы зададите линию, которая будет идти из нижнего левого угла вашего опорного вида в нижний правый угол (соответственно, это будет линия, вплотную прилегающая к его нижнему краю), а также добавите к этому виду поведение тяготения, то виды, расположенные на экране, будут двигаться под действием тяготения вниз, но не смогут «провалиться» с экрана, так как столкнутся с его нижним краем, который задается поведением столкновения.
   Если вы хотите, чтобы границы области, в которой действует поведение столкновения, совпадали с границами опорного вида, то присвойте свойству translatesReferenceBoundsIntoBoundary экземпляра поведения столкновения значение YES. Если хотите самостоятельно провести линии, соответствующие границам такой области, просто воспользуйтесь методом экземпляра addBoundaryWithIdentifier: fromPoint: toPoint:, относящимся к классу UICollisionBehavior.
   В этом примере мы собираемся создать два цветных вида, один из которых расположен на другом. После этого добавим к аниматору поведение тяготения, чтобы эти виды не перекрывали друг друга. Кроме того, они не будут выходить за границы опорного вида (то есть вида контроллера).
   Итак, для начала определим массив видов и аниматор:
 
   #import "ViewController.h"
 
   @interface ViewController ()
   @property (nonatomic, strong) NSMutableArray *squareViews;
   @property (nonatomic, strong) UIDynamicAnimator *animator;
   @end
   @implementation ViewController
 
   <# Остаток вашего кода находится здесь #>
   Потом, когда вид появится на экране, зададим поведения столкновения и тяготения и добавим их к аниматору:
   – (void)viewDidAppear:(BOOL)animated{
   [super viewDidAppear: animated];
 
   /* Создаем виды */
   NSUInteger const NumberOfViews = 2;
 
   self.squareViews = [[NSMutableArray alloc] initWithCapacity: NumberOfViews];
   NSArray *colors = @[[UIColor redColor], [UIColor greenColor]];
 
   CGPoint currentCenterPoint = self.view.center;
   CGSize eachViewSize = CGSizeMake(50.0f, 50.0f);
   for (NSUInteger counter = 0; counter < NumberOfViews; counter++){
 
   UIView *newView =
   [[UIView alloc] initWithFrame:
   CGRectMake(0.0f, 0.0f, eachViewSize.width, eachViewSize.height)];
 
   newView.backgroundColor = colors[counter];
   newView.center = currentCenterPoint;
 
   currentCenterPoint.y += eachViewSize.height + 10.0f;
 
   [self.view addSubview: newView];
 
   [self.squareViews addObject: newView];
 
   }
 
   self.animator = [[UIDynamicAnimator alloc]
   initWithReferenceView: self.view];
 
   /* Создаем тяготение */
   UIGravityBehavior *gravity = [[UIGravityBehavior alloc]
   initWithItems: self.squareViews];
   [self.animator addBehavior: gravity];
 
   /* Реализуем обнаружение столкновений */
   UICollisionBehavior *collision = [[UICollisionBehavior alloc]
   initWithItems: self.squareViews];
   collision.translatesReferenceBoundsIntoBoundary = YES;
   [self.animator addBehavior: collision];
   }
 
   Получим примерно такой же результат, как на рис. 2.1.
 
   Рис. 2.1. Взаимодействующие поведения тяготения и столкновения
 
   В этом примере показано, что поведение обнаружения столкновений работает отлично, если свойство translatesReferenceBoundsIntoBoundary имеет значение YES. Но что, если мы захотим очертить собственные границы столкновений? Здесь и пригодится метод экземпляра addBoundaryWithIdentifier: fromPoint: toPoint:, относящийся к поведению столкновения. Вот параметры, которые следует передать этому методу:
   • addBoundaryWithIdentifier – строковый идентификатор для вашей границы. Он используется для того, чтобы впоследствии вы могли получить от границы информацию о столкновении. Вы могли бы передать такой же идентификатор методу boundaryWithIdentifier: и получить в ответ объект границы. Объект относится к типу UIBezierPath и может поддерживать довольно сложные, сильно искривленные границы. Но большинство программистов предпочитают указывать простые горизонтальные или вертикальные границы, что мы и сделаем;
   • fromPoint – начальная точка границы, относится к типу CGPoint;
   • toPoint – конечная точка границы, относится к типу CGPoint.
 
   Итак, предположим, что вы хотите провести границу в нижней части опорного вида (в данном случае вида с контроллером), но не хотите, чтобы она совпадала с нижним краем. Вместо этого вам нужна граница, расположенная на 100 точек выше нижнего края. В таком случае свойство поведения столкновения translatesReferenceBoundsIntoBoundary не поможет, так как вы хотите задать иную границу, не совпадающую с пределами опорного вида. Нужно воспользоваться методом addBoundaryWithIdentifier: fromPoint: toPoint:, вот так:
 
   /* Создаем обнаружение столкновений */
   UICollisionBehavior *collision = [[UICollisionBehavior alloc]
   initWithItems: self.squareViews];
   [collision
   addBoundaryWithIdentifier:@"bottomBoundary"
   fromPoint: CGPointMake(0.0f, self.view.bounds.size.height – 100.0f)
   toPoint: CGPointMake(self.view.bounds.size.width,
   self.view.bounds.size.height – 100.0f)];
 
   [self.animator addBehavior: collision];
 
   Теперь, если мы объединим это поведение с тяготением, как делали раньше, то квадраты будут падать в опорном виде сверху вниз, но не достигнут его дна, так как проведенная нами нижняя граница находится немного выше. В рамках этого раздела я также хочу продемонстрировать возможность обнаружения столкновений между различными элементами, обладающими поведением столкновения. Класс UICollisionBehavior имеет свойство collisionDelegate, которое будет выступать в качестве делегата при обнаружении столкновений у элементов, обладающих поведением столкновения. Этот объект-делегат должен соответствовать протоколу UICollisionBehaviorDelegate. Данный протокол обладает некоторыми методами, которые мы можем реализовать. Вот два наиболее важных из этих методов: