Современная электронная библиотека ModernLib.Net

Бестселлеры O`Reilly - iOS. Приемы программирования

ModernLib.Net / Программирование / Вандад Нахавандипур / iOS. Приемы программирования - Чтение (Ознакомительный отрывок) (стр. 14)
Автор: Вандад Нахавандипур
Жанр: Программирование
Серия: Бестселлеры O`Reilly

 

 


action:@selector(handleTap:)];

[self.view addGestureRecognizer: tapGestureRecognizer];

}

Эти методы выполняют за нас всю необходимую работу. Позже, когда вид отобразится на экране, мы будем вызывать эти методы и они будут действовать.

И не забудем написать метод, который будет задавать поведение столкновения и толчковое поведение:


– (void) createAnimatorAndBehaviors{

self.animator = [[UIDynamicAnimator alloc]

initWithReferenceView: self.view];


/* Создаем обнаружение столкновений */

UICollisionBehavior *collision = [[UICollisionBehavior alloc]

initWithItems:@[self.squareView]];

collision.translatesReferenceBoundsIntoBoundary = YES;


self.pushBehavior = [[UIPushBehavior alloc]

initWithItems:@[self.squareView]

mode: UIPushBehaviorModeContinuous];


[self.animator addBehavior: collision];

[self.animator addBehavior: self.pushBehavior];

}


Подробнее о поведении столкновений рассказано в разделе 2.2. Как только мы запрограммируем все эти методы, нам понадобится вызывать их, когда вид появится на экране:


– (void)viewDidAppear:(BOOL)animated{

[super viewDidAppear: animated];


[self createGestureRecognizer];

[self createSmallSquareView];

[self createAnimatorAndBehaviors];


}


Отлично. Теперь, взглянув на файл реализации метода createGestureRecognizer, вы увидите, что мы устанавливаем регистратор жестов касаний в методе контроллера вида – этот метод называется handleTap:. В методе handleTap: вычисляем расстояние между центральной точкой маленького квадратного вида и той точкой опорного вида, до которой дотронулся пользователь. В результате имеем магнитуду силы толчка. Кроме того, рассчитаем угол между центром маленького квадратного вида и точкой касания, чтобы определить угол толчка:


– (void) handleTap:(UITapGestureRecognizer *)paramTap{


/* Получаем угол между центральной точкой квадратного вида и точкой касания */


CGPoint tapPoint = [paramTap locationInView: self.view];

CGPoint squareViewCenterPoint = self.squareView.center;


/* Вычисляем угол между центральной точкой квадратного вида и точкой касания, чтобы определить угол толчка

Формула для определения угла между двумя точками:

arc tangent 2((p1.x – p2.x), (p1.y – p2.y)) */


CGFloat deltaX = tapPoint.x – squareViewCenterPoint.x;

CGFloat deltaY = tapPoint.y – squareViewCenterPoint.y;

CGFloat angle = atan2(deltaY, deltaX);

[self.pushBehavior setAngle: angle];


/* Используем расстояние между точкой касания и центром квадратного вида для вычисления магнитуды толчка

Формула определения расстояния:

Квадратный корень из ((p1.x – p2.x)^2 + (p1.y – p2.y)^2) */


CGFloat distanceBetweenPoints =

sqrt(pow(tapPoint.x – squareViewCenterPoint.x, 2.0) +

pow(tapPoint.y – squareViewCenterPoint.y, 2.0));

[self.pushBehavior setMagnitude: distanceBetweenPoints / 200.0f];

}

Не буду чрезмерно углубляться в тригонометрию, но в этом коде используется простая формула, изучаемая в школьном курсе. По этой формуле рассчитывается угол в радианах между двумя точками. Также применяется теорема Пифагора, позволяющая узнать расстояние между двумя точками. Эти формулы вы найдете, взглянув на комментарии, которые я оставил в коде. Если же хотите подробнее разобраться с такими понятиями, как углы и радианы, рекомендую проштудировать учебник по тригонометрии.

Теперь, запустив приложение, вы сначала увидите маленький зеленый квадрат в центре экрана. Дотроньтесь до экрана в любой точке поля, окружающего квадрат (белое пространство), чтобы зеленый квадрат (вид) стал двигаться. В данном примере я беру расстояние между точкой касания и центром квадрата и делю его на 200, чтобы получить реалистичную магнитуду толчка, но вы в данном примере можете увеличить ускорение, выбрав, скажем, значение 100, а не 200. Всегда лучше экспериментировать с разными числовыми значениями, чтобы подобрать оптимальный вариант для вашего приложения.

<p>См. также</p>

Раздел 2.2.

2.4. Прикрепление нескольких динамических элементов друг к другу

<p>Постановка задачи</p>

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

<p>Решение</p>

Инстанцируйте поведение прикрепления, относящееся к типу UIAttachmentBehavior, с помощью метода экземпляра initWithItem: point: attachedToAnchor: этого класса. Добавьте это поведение к аниматору (см. раздел 2.0), отвечающему за динамику и физику движения.

<p>Обсуждение</p>

На первый взгляд поведение прикрепления может показаться непонятным. Оно сводится к следующему: вы можете задать на экране точку привязки, а затем заставить точку следовать за этой привязкой. Но я хотел бы обсудить эту возможность подробнее.

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

В этом примере мы собираемся создать такой эффект, который продемонстрирован на рис. 2.3.


Рис. 2.3. Именно такого эффекта мы хотим добиться в данном разделе с помощью поведения прикрепления


Как видите, на экране находятся три вида. Основной вид расположен в центре, в правом верхнем углу этого вида есть еще один вид, более мелкий. Маленький вид – это и есть тот элемент, который будет следовать за точкой привязки, по принципу, который я описал в примере с фотографией. Наконец, необходимо отметить, что точка привязки в данном примере будет перемещаться по экрану под действием жеста панорамирования и регистратора соответствующих жестов (см. раздел 10.3). Затем в результате таких движений станет двигаться большой вид, расположенный в центре экрана. Итак, начнем с определения необходимых свойств контроллера вида:


#import "ViewController.h"


@interface ViewController ()

@property (nonatomic, strong) UIView *squareView;

@property (nonatomic, strong) UIView *squareViewAnchorView;

@property (nonatomic, strong) UIView *anchorView;

@property (nonatomic, strong) UIDynamicAnimator *animator;

@property (nonatomic, strong) UIAttachmentBehavior *attachmentBehavior;

@end


@implementation ViewController


<# Оставшаяся часть кода контроллера вида находится здесь #>


Далее нам потребуется создать маленький квадратный вид. Но на этот раз мы поместим внутрь него еще один вид. Маленький вид, который будет располагаться в правом верхнем углу родительского вида, мы фактически соединим с точкой привязки поведения прикрепления, как было показано в примере с фотографией:


– (void) createSmallSquareView{

self.squareView =

[[UIView alloc] initWithFrame:

CGRectMake(0.0f, 0.0f, 80.0f, 80.0f)];


self.squareView.backgroundColor = [UIColor greenColor];

self.squareView.center = self.view.center;


self.squareViewAnchorView = [[UIView alloc] initWithFrame:

CGRectMake(60.0f, 0.0f, 20.0f, 20.0f)];

self.squareViewAnchorView.backgroundColor = [UIColor brownColor];

[self.squareView addSubview: self.squareViewAnchorView];


[self.view addSubview: self.squareView];

}

Далее создадим вид с точкой привязки:

– (void) createAnchorView{


self.anchorView = [[UIView alloc] initWithFrame:

CGRectMake(120.0f, 120.0f, 20.0f, 20.0f)];

self.anchorView.backgroundColor = [UIColor redColor];

[self.view addSubview: self.anchorView];


}


После этого потребуется создать регистратор жестов панорамирования и аниматор, как мы уже делали в предыдущих разделах этой главы:


– (void) createGestureRecognizer{

UIPanGestureRecognizer *panGestureRecognizer =

[[UIPanGestureRecognizer alloc] initWithTarget: self

action:@selector(handlePan:)];

[self.view addGestureRecognizer: panGestureRecognizer];

}


– (void) createAnimatorAndBehaviors{


self.animator = [[UIDynamicAnimator alloc]

initWithReferenceView: self.view];


/* Создаем распознавание столкновений */

UICollisionBehavior *collision = [[UICollisionBehavior alloc]

initWithItems:@[self.squareView]];

collision.translatesReferenceBoundsIntoBoundary = YES;


self.attachmentBehavior = [[UIAttachmentBehavior alloc]

initWithItem: self.squareView

point: self.squareViewAnchorView.center

attachedToAnchor: self.anchorView.center];

[self.animator addBehavior: collision];

[self.animator addBehavior: self.attachmentBehavior];

}


– (void)viewDidAppear:(BOOL)animated{

[super viewDidAppear: animated];


[self createGestureRecognizer];

[self createSmallSquareView];

[self createAnchorView];

[self createAnimatorAndBehaviors];

}


Как видите, мы реализуем поведение привязки с помощью его метода экземпляра initWithItem: point: attachedToAnchor:. Этот метод принимает следующие параметры:

• initWithItem – динамический элемент (в нашем примере – вид), который должен быть подключен к точке привязки;

• point – точка внутри динамического элемента, которая должна быть соединена с точкой привязки. В данном поведении центральная точка элемента используется для установки соединения с точкой привязки. Но вы можете изменить этот параметр, присвоив ему другое значение;

• attachedToAnchor – сама точка привязки, измеряемая как значение CGPoint.

Теперь, когда мы соединили верхний правый угол квадратного вида с точкой привязки (представленной как вид точки привязки), необходимо продемонстрировать, что, двигая точку привязки, мы опосредованно будем двигать и квадратный вид. Вернемся к методу createGestureRecognizer, написанному ранее. Там мы задействовали регистратор жестов касания, который будет отслеживать движение пальца пользователя по экрану. Мы решили обрабатывать регистратор жестов в методе handlePan: вида и реализуем этот метод так:


(void) handlePan:(UIPanGestureRecognizer *)paramPan{


CGPoint tapPoint = [paramPan locationInView: self.view];

[self.attachmentBehavior setAnchorPoint: tapPoint];

self.anchorView.center = tapPoint;


}


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

<p>См. также</p>

Разделы 2.0 и 10.3.

2.5. Добавление эффекта динамического зацепления к компонентам пользовательского интерфейса

<p>Постановка задачи</p>

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

<p>Решение</p>

Инстанцируйте объект типа UISnapBehavior и добавьте его к аниматору типа UIDynamicAnimator.

<p>Обсуждение</p>

Чтобы по-настоящему понять, как работает динамика зацепления, представим себе небольшое количество желе, смазанное маслом и лежащее на очень гладком столе. К желе прикреплена струна. Представляю, насколько странным вам это кажется. Но следите за мыслью. Допустим, я стою возле стола и тяну за струну, чтобы желе переместилось из исходной точки на столе в другую, выбранную вами. Поскольку желе со всех сторон покрыто маслом, оно будет плавно двигаться в этом направлении. Но раз это желе, оно, оказавшись в выбранной вами точке, еще некоторое время будет колыхаться. Именно такое поведение реализуется с помощью класса UISnapBehavior.

Один из способов практического применения такого эффекта заключается в следующем: если у вас есть приложение, на экране с которым расположено несколько видов, то, возможно, вы захотите предоставить пользователю возможность передвигать эти виды по экрану по своему желанию и самостоятельно настраивать компоновку интерфейса. Эту задачу вполне можно решить с помощью приемов, описанных в разделе 2.3, но такой вариант получится слишком негибким. Вообще техники из раздела 2.3 предназначены для решения иных задач. В этом разделе у нас есть экран, и мы добиваемся того, чтобы пользователь мог прикоснуться к любому виду на экране и переместить его. Но потом мы зацепим этот вид, ассоциировав его с точкой, в которой произошло касание.

В данном рецепте мы собираемся создать маленький вид в центре основного вида контроллера, а потом прикрепить регистратор жестов касания (см. раздел 10.5) к виду с контроллером. Всякий раз, когда пользователь прикасается к экрану в какой-то точке, мы будем зацеплять за эту точку маленький квадратный вид. Итак, приступим к определению необходимых свойств вида с контроллером:


#import "ViewController.h"


@interface ViewController ()

@property (nonatomic, strong) UIView *squareView;

@property (nonatomic, strong) UIDynamicAnimator *animator;

@property (nonatomic, strong) UISnapBehavior *snapBehavior;

@end

@implementation ViewController


<# Остальной ваш код находится здесь #>

Далее напишем метод, который будет создавать регистратор жестов касания:

– (void) createGestureRecognizer{


UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]

initWithTarget: self

action:@selector(handleTap:)];

[self.view addGestureRecognizer: tap];


}


Как и в предыдущих разделах, нам также понадобится создать маленький вид в центре экрана. Я выбрал для этой цели именно центр, но вы можете использовать в таком качестве другую точку. Этот вид мы будем сцеплять с теми точками экрана, к которым прикоснется пользователь. Итак, вот метод для создания этого вида:


– (void) createSmallSquareView{

self.squareView =

[[UIView alloc] initWithFrame:

CGRectMake(0.0f, 0.0f, 80.0f, 80.0f)];


self.squareView.backgroundColor = [UIColor greenColor];

self.squareView.center = self.view.center;


[self.view addSubview: self.squareView];

}


Переходим к созданию аниматора (см. раздел 2.0), после чего прикрепляем к нему поведение зацепления. Инициализируем поведение зацепления типа UISnapBehavior с помощью метода initWithItem: snapToPoint:. Этот метод принимает два параметра:

• initWithItem – динамический элемент (в данном случае наш вид), к которому должно применяться поведение зацепления. Как и другие динамические поведения пользовательского интерфейса, этот элемент должен соответствовать протоколу UIDynamicItem. Все экземпляры UIView по умолчанию соответствуют этому протоколу, поэтому все нормально;

• snapToPoint – точка опорного вида (см. раздел 2.0), за которую должен зацепляться динамический элемент.

Следует сделать одно важное замечание о таком зацеплении: чтобы оно работало с конкретным элементом, к аниматору уже должен быть добавлен как минимум один экземпляр зацепления для этого элемента – кроме того экземпляра, который удерживает элемент на текущей позиции. После этого все последующие зацепления будут работать правильно. Позвольте это продемонстрировать. Сейчас мы реализуем метод, который будет создавать поведение зацепления и аниматор, а потом добавлять это поведение к аниматору:


– (void) createAnimatorAndBehaviors{

self.animator = [[UIDynamicAnimator alloc]

initWithReferenceView: self.view];


/* Создаем обнаружение столкновений */

UICollisionBehavior *collision = [[UICollisionBehavior alloc]

initWithItems:@[self.squareView]];

collision.translatesReferenceBoundsIntoBoundary = YES;


[self.animator addBehavior: collision];


/* Пока зацепляем квадратный вид с его актуальным центром */

self.snapBehavior = [[UISnapBehavior alloc]

initWithItem: self.squareView

snapToPoint: self.squareView.center];

self.snapBehavior.damping = 0.5f; /* Medium oscillation */

[self.animator addBehavior: self.snapBehavior];

}


Как видите, здесь мы зацепляем небольшой квадратный вид, связывая его с текущим центром, – в сущности, просто оставляем его на месте. Позже, когда мы регистрируем на экране жесты касания, мы обновляем поведение зацепления. Кроме того, необходимо отметить, что мы задаем для этого поведения свойство damping. Это свойство будет управлять эластичностью, с которой элемент будет зацеплен за точку. Чем выше значение, тем меньше эластичность, соответственно, тем слабее «колышется» элемент. Здесь можно задать любое значение в диапазоне от 0 до 1. Теперь, когда вид появится на экране, вызовем эти методы, чтобы инстанцировать маленький квадратный вид, установить регистратор жестов касания, а также настроить аниматор и поведение зацепления:


– (void)viewDidAppear:(BOOL)animated{

[super viewDidAppear: animated];


[self createGestureRecognizer];

[self createSmallSquareView];

[self createAnimatorAndBehaviors];

}


После создания регистратора жестов касания в методе createGestureRecognizer вида с контроллером мы приказываем регистратору сообщать о таких касаниях методу handleTap: вида с контроллером. В этом методе мы получаем точку, в которой пользователь прикоснулся к экрану, после чего обновляем поведение зацепления.

Здесь необходимо отметить, что вы не сможете просто обновить существующее поведение – потребуется повторно его инстанцировать. Итак, прежде, чем мы инстанцируем новый экземпляр поведения зацепления, понадобится удалить старый экземпляр (при его наличии), а потом добавить к аниматору новый. У каждого аниматора может быть всего одно поведение зацепления, ассоциированное с конкретным динамическим элементом, в данном случае с маленьким квадратным видом. Если добавить к одному и тому же аниматору несколько поведений зацепления, относящихся к одному и тому же динамическому элементу, то аниматор проигнорирует все эти поведения, так как не будет знать, какое из них выполнять первым. Поэтому, чтобы поведения зацепления работали, сначала удалите все зацепления для этого элемента из вашего аниматора, воспользовавшись его методом removeBehavior:, а потом добавьте новое поведение зацепления следующим образом:


– (void) handleTap:(UITapGestureRecognizer *)paramTap{


/* Получаем угол между центром квадратного вида и точкой касания */


CGPoint tapPoint = [paramTap locationInView: self.view];


if (self.snapBehavior!= nil){

[self.animator removeBehavior: self.snapBehavior];

}


self.snapBehavior = [[UISnapBehavior alloc] initWithItem: self.squareView

snapToPoint: tapPoint];

self.snapBehavior.damping = 0.5f; /* Средняя осцилляция */

[self.animator addBehavior: self.snapBehavior];

}

<p>См. также</p>

Разделы 2.0 и 10.5.

2.6. Присваивание характеристик динамическим эффектам

<p>Постановка задачи</p>

Вероятно, вас устраивает стандартная физика, по умолчанию встроенная в динамические поведения из UIKit. Но при работе с элементами, которыми вы управляете с помощью динамических поведений, может потребоваться присваивать этим элементам и иные характеристики, например массу и эластичность.

<p>Решение</p>

Инстанцируйте объект типа UIDynamicItemBehavior и присвойте ему ваши динамические элементы. После инстанцирования пользуйтесь различными свойствами этого класса для изменения характеристик динамических элементов. Затем добавьте это поведение к аниматору (см. раздел 2.0) – и все остальное аниматор сделает за вас.

<p>Обсуждение</p>

Динамические поведения отлично подходят для добавления реалистичной физики к элементам, соответствующим протоколу UIDynamicItem, например ко всем видам типа UIView. Но в некоторых приложениях вам может понадобиться явно указать характеристики конкретного элемента. Например, в приложении, где используются эффекты тяготения и столкновения (см. разделы 2.1 и 2.2), вы, возможно, захотите указать, что один из элементов на экране, подвергающийся действию тяготения и столкновениям, должен отскакивать от границы сильнее, чем другой элемент. В другом случае, возможно, захотите указать, что в ходе воплощения различных визуальных эффектов, применяемых аниматором к элементу, этот элемент вообще не должен вращаться.

Подобные задачи решаются без труда с помощью экземпляров класса UIDynamicItemBehavior. Эти экземпляры также являются динамическими поведениями, и мы можем добавлять их к аниматору с помощью метода экземпляра addBehavior:, относящегося к классу UIDynamicAnimator, – в этой главе мы так уже делали. Когда вы инициализируете экземпляр этого класса, вы вызываете метод-инициализатор initWithItems: и передаете ваш вид либо какой угодно объект, соответствующий протоколу UIDynamicItem. В качестве альтернативы можете инициализировать экземпляр элемента с динамическим поведением с помощью метода init, а потом добавлять к этому поведению разнообразные объекты, пользуясь методом addItem:.

Экземпляры класса UIDynamicItemBehavior обладают настраиваемыми свойствами, которые вы можете корректировать, чтобы модифицировать поведение динамических элементов (например, видов). Далее перечислены и объяснены некоторые наиболее важные свойства этого класса.

• allowsRotation – логическое значение. Если оно равно YES, то, как понятно из названия, это свойство позволяет аниматору вращать динамические элементы в ходе применения к ним визуальных эффектов. В идеале вы должны устанавливать значение этого свойства в YES, если желаете имитировать реалистичную физику, но если по каким-то причинам в приложении необходимо гарантировать, что определенный элемент ни при каких условиях вращаться не будет, задайте для этого свойства значение NO и прикрепите элемент к этому поведению.

• resistance – сопротивление элемента движению. Это свойство может иметь значения в диапазоне от 0 до CGFLOAT_MAX. Чем выше значение, тем сильнее будет сопротивление, оказываемое элементом воздействующим на него силам (тем силам, которые вы к нему прикладываете). Например, если вы добавите к аниматору поведение тяготения и создадите в центре экрана вид с сопротивлением CGFLOAT_MAX, то тяготение не сможет сдвинуть этот вид к центру экрана. Вид просто останется там, где вы его создали.

• friction – это значение с плавающей точкой в диапазоне от 0 до 1. Оно указывает силу трения, которая должна действовать на края данного элемента, когда другие элементы соударяются с ним или проскальзывают по его краю. Чем выше значение, тем больше сила трения, применяемая к элементу.


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

• elasticity – это значение с плавающей точкой в диапазоне от 0 до 1. Оно указывает, насколько эластичным должен быть элемент. Чем выше это значение, тем более пластичным и «желеобразным» будет этот элемент с точки зрения аниматора. О том, что такое эластичность, подробно рассказано в разделе 2.5.

• density – это значение с плавающей точкой в диапазоне от 0 до 1 (по умолчанию применяется значение 1). Оно не используется непосредственно для воздействия на поведение динамических поведений элементов, но с его помощью аниматор рассчитывает массу объектов и то, как эта масса отражается на визуальных эффектах. Например, если вы столкнете один элемент с другим (см. раздел 2.3), но у одного из них будет плотность 1, а у другого – 0,5, то при одинаковых высоте и ширине обоих элементов масса первого элемента будет больше, чем масса второго. Аниматор вычисляет массу элементов, исходя из их плотности и размеров экрана. Поэтому если вы столкнете маленький вид с высокой плотностью с большим видом с очень низкой плотностью, то маленький вид, в зависимости от конкретного размера и плотности этого элемента, может быть воспринят аниматором как более массивный объект, нежели крупный вид. В таком случае, аниматор может сильнее оттолкнуть тот элемент, который на экране кажется более крупным, тогда как толчок, сообщаемый крупным элементом мелкому, получится не столь значительным.


  • Страницы:
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15