Вступление
В первом уроке этого короткого цикла по UIKit Dynamics мы изучили основы API, создав анимированный компонент меню. В этом уроке мы продолжим работу над нашим проектом и внедрим еще один анимированный компонент — настраиваемое представление предупреждений.
1. Обзор
Представление предупреждений по умолчанию на iOS великолепно, но оно не очень настраиваемо с точки зрения внешнего вида и поведения. Если вам нужно настраиваемое представление предупреждений, вам нужно создать собственное решение, и именно это мы и сделаем в этом руководстве. Основное внимание в этом руководстве уделяется поведению представления предупреждений, а не его функциональности. Давайте посмотрим, каков результат того, что мы после
Представлением оповещения будет экземпляр UIView
к которому мы добавим следующие подпредставления:
- объект
UILabel
для отображения заголовка представления предупреждения - объект
UILabel
для отображения сообщения представления предупреждения - один или несколько экземпляров
UIButton
позволяющих пользователю взаимодействовать с представлением предупреждений
Мы будем использовать класс UISnapBehavior
для представления представления предупреждений. Как видно из названия, этот подкласс UIDynamicBehavior
заставляет динамический элемент привязываться к точке, как если бы он был притянут к ней магнитным UIDynamicBehavior
.
Класс UISnapBehavior
определяет одно дополнительное свойство — damping
, которое определяет величину колебаний, когда динамический элемент достигает точки, к которой он притягивается.
Мы будем использовать гравитационное поведение в сочетании с столкновением и толчком, чтобы отклонить представление оповещения. Помните, что мы уже использовали это поведение в предыдущем уроке .
Вид оповещения будет анимирован в верхней части экрана. Когда представление оповещения собирается появиться, поведение привязки заставит его упасть в представлении и привязаться к центру экрана. Чтобы отклонить представление предупреждений, поведение толчка на короткое время подтолкнет его к нижней части экрана, а затем поведение гравитации потянет его к верхней части экрана и заставит анимировать его за пределами экрана.
Мы создадим пользовательский метод инициализации для компонента представления оповещения, который принимает заголовок оповещения, сообщение, заголовок кнопки и его родительское представление. Мы не будем реализовывать протокол делегата для представления предупреждений. Вместо этого мы будем использовать блоки, что делает его более элегантным и современным решением. Блок или обработчик будет принимать два параметра: индекс и название кнопки, которую пользователь нажал.
Мы также отобразим полупрозрачное представление позади представления предупреждений, чтобы предотвратить взаимодействие пользователя с его родительским представлением, пока представление предупреждений является видимым. Давайте начнем с рассмотрения свойств представления оповещения и пользовательского инициализатора.
2. Свойства и инициализация
Шаг 1. Создание класса представления предупреждений
Нажмите Command-N на клавиатуре, чтобы создать новый файл, и выберите класс Objective-C из списка шаблонов iOS . Сделайте его подклассом NSObject и назовите его AlertComponent .
Шаг 2: Объявление свойств
Следующим шагом является объявление нескольких частных свойств. Откройте AlertComponent.m , добавьте расширение класса вверху и объявите следующие свойства:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@interface AlertComponent ()
@property (nonatomic, strong) UIView *alertView;
@property (nonatomic, strong) UIView *backgroundView;
@property (nonatomic, strong) UIView *targetView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *messageLabel;
@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *message;
@property (nonatomic, strong) NSArray *buttonTitles;
@property (nonatomic) CGRect initialAlertViewFrame;
@end
|
Функция каждого свойства станет понятной по мере реализации компонента оповещения. Пришло время создать пользовательский инициализатор компонента.
Шаг 3: Инициализация
Как я уже упоминал, мы собираемся использовать собственный инициализатор, чтобы максимально упростить работу с компонентом оповещения. Инициализатор принимает четыре параметра: заголовок оповещения, его сообщение, заголовки кнопок и представление, к которому будет добавлен компонент оповещения, его родительское представление. Откройте AlertComponent.h и добавьте следующее объявление:
1
2
3
4
5
|
@interface AlertComponent : NSObject
— (id)initAlertWithTitle:(NSString *)title andMessage:(NSString *)message andButtonTitles:(NSArray *)buttonTitles andTargetView:(UIView *)targetView;
@end
|
3. Настройка просмотра предупреждений
Шаг 1: Объявление методов настройки
В этой части будет настроено представление оповещений, и все его подпредставления будут добавлены к нему. Также будет настроен как фоновый вид, так и динамический аниматор.
Откройте AlertComponent.m и объявите следующие частные методы в расширении частного класса:
1
2
3
4
5
6
7
8
|
@interface AlertComponent()
…
— (void)setupBackgroundView;
— (void)setupAlertView;
@end
|
Имена методов говорят сами за себя. Давайте начнем с реализации метода setupAlertView
так как большая часть настройки оповещения происходит в этом методе.
Шаг 2. Настройка вида оповещения
В setupAlertView
мы делаем три вещи:
- инициализировать и настроить представление предупреждений
- инициализировать и настроить метки представления предупреждений
- инициализировать и настроить кнопки просмотра предупреждений
Давайте начнем с вычисления размера и положения представления оповещения, как показано в фрагменте кода ниже.
1
2
3
4
5
6
7
|
— (void)setupAlertView {
// Set the size of the alert view.
CGSize alertViewSize = CGSizeMake(250.0, 130.0 + 50.0 * self.buttonTitles.count);
// Set the initial origin point depending on the direction of the alert view.
CGPoint initialOriginPoint = CGPointMake(self.targetView.center.x, self.targetView.frame.origin.y — alertViewSize.height);
}
|
Мы начнем с установки размера представления предупреждений. Чтобы сделать вид оповещения динамичным, мы добавляем 50.0
точек к его высоте для каждой кнопки. Также обратите внимание, что исходный источник представления предупреждений находится за пределами экрана. Следующим шагом является инициализация и настройка представления предупреждений:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
self.alertView = [[UIView alloc] initWithFrame:CGRectMake(initialOriginPoint.x, initialOriginPoint.y, alertViewSize.width, alertViewSize.height)];
// Background color.
[self.alertView setBackgroundColor:[UIColor colorWithRed:0.94 green:0.94 blue:0.94 alpha:1.0]];
// Make the alert view with rounded corners.
[self.alertView.layer setCornerRadius:10.0];
// Set a border to the alert view.
[self.alertView.layer setBorderWidth:1.0];
[self.alertView.layer setBorderColor:[UIColor blackColor].CGColor];
// Assign the initial alert view frame to the respective property.
self.initialAlertViewFrame = self.alertView.frame;
|
Используя alertViewSize
и initialOriginPoint
, мы инициализируем объект alertView
и устанавливаем его цвет фона. Мы cornerRadius
углы представления предупреждений, устанавливая значение угла layer
его layer
cornerRadius
, его borderWidth
1.0
, а borderColor
— черного цвета. Мы также сохраняем начальный кадр представления оповещения в его initialAlertViewFrame
как оно понадобится нам позже.
Если XCode сообщает, что не знает о alertView
layer
alertView
, то добавьте следующий оператор import вверху файла реализации:
1
|
#import <QuartzCore/QuartzCore.h>
|
Пришло время добавить ярлыки. Давайте начнем с заголовка.
1
2
3
4
5
6
7
8
|
// Setup the title label.
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 10.0, self.alertView.frame.size.width, 40.0)];
[self.titleLabel setText:self.title];
[self.titleLabel setTextAlignment:NSTextAlignmentCenter];
[self.titleLabel setFont:[UIFont fontWithName:@»Avenir-Heavy» size:14.0]];
// Add the title label to the alert view.
[self.alertView addSubview:self.titleLabel];
|
Настройка метки сообщения очень похожа.
01
02
03
04
05
06
07
08
09
10
|
// Setup the message label.
self.messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, self.titleLabel.frame.origin.y + self.titleLabel.frame.size.height, self.alertView.frame.size.width, 80.0)];
[self.messageLabel setText:self.message];
[self.messageLabel setTextAlignment:NSTextAlignmentCenter];
[self.messageLabel setFont:[UIFont fontWithName:@»Avenir» size:14.0]];
[self.messageLabel setNumberOfLines:3];
[self.messageLabel setLineBreakMode:NSLineBreakByWordWrapping];
// Add the message label to the alert view.
[self.alertView addSubview:self.messageLabel];
|
Обратите внимание, что для свойства numberOfLines
установлено значение 3
а для lineBreakMode
установлено значение NSLineBreakByWordWrapping
.
Последнее, что нам нужно настроить, это кнопки просмотра предупреждений. Несмотря на то, что количество кнопок может варьироваться, их настройка и расположение довольно просты. Мы разделяем кнопки на 5
пунктов и используем цикл for
для их инициализации.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
CGFloat lastSubviewBottomY = self.messageLabel.frame.origin.y + self.messageLabel.frame.size.height;
for (int i=0; i<[self.buttonTitles count]; i++) {
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10.0, lastSubviewBottomY + 5.0, self.alertView.frame.size.width — 20.0, 40.0)];
[button setTitle:[self.buttonTitles objectAtIndex:i] forState:UIControlStateNormal];
[button.titleLabel setFont:[UIFont fontWithName:@»Avenir» size:13.0]];
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[button setTitleColor:[UIColor yellowColor] forState:UIControlStateHighlighted];
[button setBackgroundColor:[UIColor colorWithRed:0.0 green:0.47 blue:0.39 alpha:1.0]];
[button addTarget:self action:@selector(handleButtonTap:) forControlEvents:UIControlEventTouchUpInside];
[button setTag:i + 1];
[self.alertView addSubview:button];
lastSubviewBottomY = button.frame.origin.y + button.frame.size.height;
}
|
Обратите внимание, что каждая кнопка вызывает метод handleButtonTap:
при нажатии. Мы можем определить, какую кнопку пользователь нажал, tag
свойство tag
кнопки.
Наконец, добавьте представление оповещения в целевое или родительское представление, добавив следующую строку в нижней части метода setupAlertView:
1
2
|
// Add the alert view to the parent view.
[self.targetView addSubview:self.alertView];
|
Шаг 3: Настройка фонового представления
Второй метод, который нам нужно реализовать, это setupBackgroundView
. Фоновое представление не позволит пользователю взаимодействовать с родительским представлением оповещения, пока отображается оповещение. Мы изначально установили его alpha
свойство на 0.0
, что означает, что оно прозрачно.
1
2
3
4
5
6
|
— (void)setupBackgroundView {
self.backgroundView = [[UIView alloc] initWithFrame:self.targetView.frame];
[self.backgroundView setBackgroundColor:[UIColor grayColor]];
[self.backgroundView setAlpha:0.0];
[self.targetView addSubview:self.backgroundView];
}
|
Шаг 4: Реализация инициализатора
Когда setupAlertView
и setupBackgroundView
готовы к использованию, давайте реализуем пользовательский инициализатор, который мы объявили ранее.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
— (id)initAlertWithTitle:(NSString *)title andMessage:(NSString *)message andButtonTitles:(NSArray *)buttonTitles andTargetView:(UIView *)targetView {
if (self = [super init]) {
// Assign the parameter values to local properties.
self.title = title;
self.message = message;
self.targetView = targetView;
self.buttonTitles = buttonTitles;
// Setup the background view.
[self setupBackgroundView];
// Setup the alert view.
[self setupAlertView];
// Setup the animator.
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.targetView];
}
return self;
}
|
Мы устанавливаем свойства title
, message
, targetView
и buttonTitles
, вызываем setupBackgroundView
и setupAlertView
и инициализируем динамический аниматор, передавая self.targetView
качестве своего ссылочного представления.
4. Отображение вида оповещения
Чтобы показать представление оповещения после его инициализации, нам нужно объявить и реализовать открытый метод, который может быть вызван, например, контроллером представления, в котором размещено представление оповещения. Откройте AlertComponent.h и добавьте следующее объявление метода:
1
|
— (void)showAlertView;
|
Вернитесь к AlertComponent.m, чтобы реализовать showAlertView
. Как я упоминал ранее в этом руководстве, мы будем использовать новый подкласс UIDynamicBehavior
для отображения представления предупреждений, UISnapBehavior
. Давайте посмотрим, как мы используем этот класс в showAlertView
.
01
02
03
04
05
06
07
08
09
10
11
12
|
— (void)showAlertView {
[self.animator removeAllBehaviors];
UISnapBehavior *snapBehavior = [[UISnapBehavior alloc] initWithItem:self.alertView snapToPoint:self.targetView.center];
snapBehavior.damping = 0.8;
[self.animator addBehavior:snapBehavior];
[UIView animateWithDuration:0.75 animations:^{
[self.backgroundView setAlpha:0.5];
}];
}
|
Мы начнем с удаления любых существующих динамических поведений из динамического аниматора, чтобы избежать появления конфликтов. Помните, что некоторые динамические поведения могут быть добавлены к динамическому аниматору только один раз, например, гравитационное поведение. Кроме того, мы добавим другие динамические поведения, чтобы закрыть представление предупреждений.
Как видите, использование привязки не сложно. Мы указываем, к какому динамическому элементу следует применить поведение, и задаем точку, к которой должен привязываться динамический элемент. Мы также устанавливаем свойство damping
поведения, как мы обсуждали ранее. Также обратите внимание, что мы анимируем свойство alpha
фонового представления.
Чтобы проверить представление предупреждений, нам нужно внести некоторые изменения в класс ViewController
. Давайте начнем с добавления экземпляра UIButton
в представление контроллера представления, чтобы показать представление предупреждения. Откройте Main.storyboard и перетащите экземпляр UIButton
из библиотеки объектов в представление контроллера представления. Расположите кнопку в нижней части представления и присвойте ей заголовок « Показать представление предупреждений» . Добавьте действие в ViewController.h, как показано ниже.
1
2
3
4
5
|
@interface ViewController : UIViewController
— (IBAction)showAlertView:(id)sender;
@end
|
Вернитесь к раскадровке и подключите действие контроллера представления к кнопке. Откройте ViewController.m и импортируйте файл AlertComponent
класса AlertComponent
.
1
|
#import «AlertComponent.h»
|
Затем объявите свойство в расширении частного класса типа AlertComponent
и назовите его alertComponent
.
1
2
3
4
5
6
7
8
|
@interface ViewController ()
@property (nonatomic, strong) MenuComponent *menuComponent;
@property (nonatomic, strong) AlertComponent *alertComponent;
— (void)showMenu:(UIGestureRecognizer *)gestureRecognizer;
@end
|
Затем мы инициализируем компонент оповещения в методе viewDidLoad
контроллера представления.
1
2
3
4
5
6
7
8
9
|
— (void)viewDidLoad {
…
// Initialize Alert Component
self.alertComponent = [[AlertComponent alloc] initAlertWithTitle:@»Custom Alert»
andMessage:@»You have a new e-mail message, but I don’t know from whom.»
andButtonTitles:@[@»Show me», @»I don’t care», @»For me, really?»]
andTargetView:self.view];
}
|
Чтобы показать компонент оповещения, вызовите showAlertView:
в только что созданном showAlertView:
1
2
3
|
— (IBAction)showAlertView:(id)sender {
[self.alertComponent showAlertView];
}
|
Запустите ваше приложение и нажмите кнопку, чтобы отобразить вид предупреждения. Результат должен выглядеть примерно так, как показано ниже.
5. Скрытие вида оповещения
Как мы видели ранее, handleButtonTap:
метод вызывается, когда пользователь нажимает кнопку представления предупреждений. Окно оповещения должно скрываться при нажатии одной из кнопок. Посмотрим, как это работает.
Пересмотрите AlertComponent.m и, в расширении частного класса, объявите handleButtonTap:
метод.
1
2
3
4
5
6
7
|
@interface AlertComponent()
…
— (void)handleButtonTap:(UIButton *)sender;
@end
|
В этом методе мы создаем ряд динамических поведений и добавляем их в объект динамического аниматора. Нам нужно динамическое поведение:
- гравитационное поведение, которое притягивает внимание к верхней части экрана
- поведение при столкновении с границей за пределами экрана, которая останавливает просмотр предупреждений
- поведение толчка, при котором вид предупреждения немного подталкивает к нижней части экрана
После удаления существующего поведения из динамического аниматора и инициализации поведения push, как показано ниже.
1
2
3
4
5
6
7
8
|
— (void)handleButtonTap:(UIButton *)sender {
// Remove all behaviors from animator.
[self.animator removeAllBehaviors];
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.alertView] mode:UIPushBehaviorModeInstantaneous];
[pushBehavior setAngle:M_PI_2 magnitude:20.0];
[self.animator addBehavior:pushBehavior];
}
|
Свойство angle
поведения толчка определяет направление толчка. Устанавливая угол на M_PI_2
, сила поведения толчка направлена к нижней части экрана.
Следующий шаг — добавление гравитационного поведения. Вектор, который мы передаем в setGravityDirection
приведет к силе в направлении верхней части экрана, вытянув вид предупреждения вверх.
1
2
3
|
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.alertView]];
[gravityBehavior setGravityDirection:CGVectorMake(0.0, -1.0)];
[self.animator addBehavior:gravityBehavior];
|
Что интересно в поведении столкновений, так это то, что мы определяем границу за пределами экрана.
1
2
3
4
5
6
|
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.alertView]];
[collisionBehavior addBoundaryWithIdentifier:@»alertCollisionBoundary»
fromPoint:CGPointMake(self.initialAlertViewFrame.origin.x, self.initialAlertViewFrame.origin.y — 10.0)
toPoint:CGPointMake(self.initialAlertViewFrame.origin.x + self.initialAlertViewFrame.size.width, self.initialAlertViewFrame.origin.y — 10.0)];
[self.animator addBehavior:collisionBehavior];
|
Нам также нужно динамическое поведение элемента для настройки эластичности столкновения. В результате представление предупреждений немного отскочит при столкновении с границей за пределами экрана.
1
2
3
|
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.alertView]];
itemBehavior.elasticity = 0.4;
[self.animator addBehavior:itemBehavior];
|
Нам также нужно снова сделать фоновый вид прозрачным. Мы делаем это, устанавливая alpha
свойство фонового представления в 0.0
в блоке анимации.
1
2
3
|
[UIView animateWithDuration:2.0 animations:^{
[self.backgroundView setAlpha:0.0];
}];
|
Запустите ваше приложение еще раз, чтобы увидеть результат.
6. Обработка взаимодействия с пользователем
Хотя представление предупреждений реагирует на взаимодействие с пользователем, в настоящее время мы не знаем, какую кнопку нажал пользователь. Это то, на чем мы сосредоточимся в этом разделе.
Как и в случае с компонентом меню, мы собираемся использовать блоки для решения этой проблемы. Блоки создают элегантное решение и часто могут быть проще в использовании, чем протокол делегирования.
Начнем с обновления публичного метода showAlertView
. Метод должен принять обработчик завершения, который вызывает представление оповещения, когда пользователь нажал одну из кнопок. В AlertComponent.h обновите объявление метода showAlertView
из:
1
|
— (void)showAlertView;
|
чтобы:
1
|
— (void)showAlertViewWithSelectionHandler:(void(^)(NSInteger buttonIndex, NSString *buttonTitle))handler;
|
Обработчик завершения принимает два параметра: индекс типа NSInteger
и заголовок типа NSString
кнопки, которая была нажата пользователем. Если мы хотим вызвать обработчик завершения, когда пользователь нажимает кнопку представления предупреждений, нам нужно сохранить ссылку на обработчик завершения. Это означает, что нам нужно объявить свойство для обработчика завершения. Мы делаем это в расширении частного класса в AlertComponent.m .
1
2
3
4
5
6
7
8
9
|
@interface AlertComponent ()
…
@property (nonatomic, strong) void(^selectionHandler)(NSInteger, NSString *);
…
@end
|
По-прежнему в AlertComponent.m обновите описание метода, как мы это делали в заголовочном файле минуту назад, и сохраните обработчик завершения в свойстве selectionHandler
, которое мы только что объявили.
1
2
3
4
5
|
— (void)showAlertViewWithSelectionHandler:(void (^)(NSInteger, NSString *))handler {
self.selectionHandler = handler;
…
}
|
Последний фрагмент головоломки вызывает обработчик завершения в handleButtonTap:
передавая тег и заголовок кнопки.
1
2
3
4
5
6
|
— (void)handleButtonTap:(UIButton *)sender {
// Call the selection handler.
self.selectionHandler(sender.tag, sender.titleLabel.text);
…
}
|
Компонент AlertComponent завершен. Пришло время проверить все. Вернитесь к ViewController.m и обновите действие showAlertView: как показано ниже. Как вы можете видеть, мы вызываем новый showAlertViewWithSelectionHandler:
метод и передаем блок, который будет вызываться, когда пользователь нажимает кнопку в представлении предупреждений.
1
2
3
4
5
|
— (IBAction)showAlertView:(id)sender {
[self.alertComponent showAlertViewWithSelectionHandler:^(NSInteger buttonIndex, NSString *buttonTitle) {
NSLog(@»%ld, %@», (long)buttonIndex, buttonTitle);
}];
}
|
Вот и все. Запустите ваше приложение еще раз и осмотрите консоль Xcode, чтобы увидеть результат нашей работы.
Вывод
UIKit Dynamics впервые появился в iOS 7 и может помочь вам быстро создавать реалистичные анимации. Эта небольшая серия иллюстрирует, что использование UIKit Dynamics в ваших проектах не сложно, и вам не нужно быть экспертом по математике или физике.
Обратите внимание, что UIKit Dynamics в первую очередь предназначен для использования в приложениях, основанных на представлении. Если вы ищете подобное решение для игр, то я рекомендую взглянуть на Apple Sprite Kit , который предназначен для разработки игр.