Этот учебник научит вас комбинировать UIGestureRecognizers
чтобы заставить пользователей делать нетривиальные жесты для выполнения действия. Мы будем использовать пример погашения цифрового билета, используя реальную аналогию разрыва его пополам.
Обзор проекта
UIGestureRecognizer
и его различные подклассы позволяют легко распознавать различные жесты на назначенных целевых объектах и выполнять действия, когда эти жесты распознаются. Можно комбинировать различные UIGestureRecognizers
чтобы заставить людей делать нетривиальные жесты для выполнения действия. Это может быть использовано в тех случаях, когда пользователи пытаются инициировать необратимое действие, когда простой, подверженный несчастным случаям жест, такой как касание или пролистывание, не подходит.
Билеты указывают на право допуска на различные мероприятия или места. Они обычно перфорированы, чтобы показать, что билет был использован и больше не подлежит обмену. Apple представила приложение Passbook, которое позволяет людям использовать свой телефон в качестве билета или другой формы мобильного платежа. Passbook ограничен представлением билетов с QR-кодом в качестве единственного метода их погашения.
Авиакомпании и крупные сети инвестировали в QR-сканеры, чтобы начать принимать цифровые билеты. Например, небольшие заведения, бары и ночные клубы могут не иметь возможности или не хотят вкладывать средства в новое оборудование. Тем не менее, они были вынуждены предлагать покупку билетов онлайн, так как все больше людей требуют этого. К сожалению, по умолчанию эти онлайн-билеты погашаются путем сравнения номера заказа с основным списком. Понятно, что это не масштабируемо, поэтому продавцы и покупатели цифровых билетов оказываются не в идеальной ситуации.
Мы не хотим, чтобы билеты были случайно выкуплены простым жестом. Почему бы нам не воссоздать реальный метод выкупа билета, оторвав заглушку от основной части билета, которую обычно удерживает человек, проверяющий билет? Это может быть реализовано с помощью распознавателя жестов длинного нажатия на заглушке и распознавателя жестов смахивания на основной части. Думайте об этом, как окунь и отрыв другой части.
1. Создать проект
Откройте Xcode и выберите «Создать новый проект Xcode». Выберите «Очистить приложение» и нажмите «Далее». Введите название для вашего проекта, я назвал мои «Билеты». Введите название своей организации, идентификатор компании и префикс класса. Выберите iPhone из устройств и просто выберите «Использовать автоматический подсчет ссылок». Нам не нужны юнит-тесты или базовые данные для этого проекта.
2. Создайте контроллеры представления
Шаг 1
Этот проект будет иметь два контроллера представления, один экран для представления списка всех доступных билетов и один экран, где билеты фактически выкуплены.
Создайте подкласс UITableViewController
под названием «TicketListViewController». Этот контроллер не требует XIB для пользовательского интерфейса, поскольку это просто список.
Шаг 2
Создайте подкласс UIViewController
под названием «RedeemTicketViewController». Выберите «С XIB для пользовательского интерфейса», поскольку этот экран будет иметь более сложный дизайн.
Шаг 3
Теперь создайте навигацию через приложение. В верхней части вашего приложения делегат импортирует TicketListViewController.h
.
1
|
#import “TicketListViewController.h”
|
В application:didFinishLaunchingWithOptions:
создайте экземпляр контроллера представления списка application:didFinishLaunchingWithOptions:
и экземпляр UINavigationController
с контроллером представления списка application:didFinishLaunchingWithOptions:
качестве корневого контроллера представления. Установите корневой контроллер окна в качестве контроллера навигации.
01
02
03
04
05
06
07
08
09
10
11
|
— (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
TicketListViewController* vc = [[TicketListViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:vc];
[self.window setRootViewController:navController];
[self.window makeKeyAndVisible];
return YES;
}
|
Создайте и запустите приложение, чтобы убедиться, что все правильно подключено. Вы должны увидеть пустой экран на экране.
3. Создайте список билетов
Шаг 1
Для этого примера мы жестко закодируем билеты в приложение. Вам нужно будет изменить этот раздел на более динамичный, когда у вас есть доступ к базе данных, в которой хранятся продажи билетов.
Отредактируйте метод init
в TicketListViewController
чтобы установить заголовок панели навигации для этого экрана. Это поможет пользователям ориентироваться в приложении.
01
02
03
04
05
06
07
08
09
10
|
— (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
self.navigationItem.title = @»Tickets»;
}
return self;
}
|
Шаг 2
Измените источник данных табличного представления, чтобы иметь один раздел и одну строку. Создайте ячейку со стилем UITableViewCellStyleSubtitle
. Установите текстовую метку для отображения имени события и места, текстовую метку для отображения даты события и тип аксессуара UITableViewCellAccessoryDisclosureIndicator
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#pragma mark — Table view data source
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @»Cell»;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = @»Special Guests at Generic Venue»;
cell.detailTextLabel.text = @»19 Jun 2013″;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
|
Шаг 3
В верхней части TicketListViewController.m
импортируйте RedeemTickerViewController.h
.
1
|
#import “RedeemTickerViewController.h”
|
Теперь измените делегат табличного представления таким образом, чтобы при нажатии строки пользователь выводился на экран погашения квитанции.
1
2
3
4
5
6
7
8
9
|
#pragma mark — Table view delegate
— (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
RedeemTicketViewController* vc = [[RedeemTicketViewController alloc]initWithNibName:@»RedeemTicketViewController» bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
}
|
Шаг 4
Создайте и запустите приложение, чтобы убедиться, что навигация работает правильно. Нажатие на строку приведет вас к пустому представлению с кнопкой возврата.
4. Создайте экран погашения билетов
Шаг 1
Это самый интересный экран с большей интерактивностью и функциональностью.
В RedeemTickerViewController.h
создайте выход для заглушки билета UIImageView
, основной части билета UIImageView
и UIView
который позволит нам анимировать билет, как будто он отрывается. BOOL
переменную экземпляра BOOL
которую мы будем использовать для отслеживания того, удерживается ли квитанция на месте. Создайте UILongPressGestureRecognizer
и UISwipeGestureRecognizer
который мы будем использовать позже.
Файл заголовка теперь должен быть таким:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
#import <UIKit/UIKit.h>
@interface RedeemTicketViewController : UIViewController
@property (nonatomic, strong) IBOutlet UIView* containerView;
@property (nonatomic, strong) IBOutlet UIImageView* ticketStub;
@property (nonatomic, strong) IBOutlet UIImageView* ticketMain;
@property (nonatomic, strong) UILongPressGestureRecognizer* longPressGest;
@property (nonatomic, strong) UISwipeGestureRecognizer* swipeGest;
@property (nonatomic, assign) BOOL isHeld;
@end
|
Шаг 2
Откройте RedeemTicketViewController.xib
и выберите объект представления контроллера представления. Снимите флажок «Использовать Autolayout» на панели «Инспектор файлов», чтобы включить совместимость до iOS6. Мы установим маску авторазмера для каждого вида сами, чтобы гарантировать, что теперь мы учитываем различные размеры экрана на iPhone.
Теперь добавьте представление изображения из библиотеки объектов. Это будет для заглушки ticketStub
, поэтому подключите выходной ticketStub
владельца файла ticketStub
к представлению изображения. Установите размер рамки вида изображения, чтобы быть в начале (20, 20)
и иметь размер (280, 100)
. Установите маску автоматического изменения размера, чтобы иметь фиксированное верхнее поле и не изменять размеры по ширине или высоте.
Теперь добавьте вид из библиотеки объектов. Это будет представление контейнера, поэтому подключите к представлению выход containerView
владельца файла. Установите рамку вида на (20, 120)
и размером (280, 280)
. Установите маску автоматического изменения размера, чтобы иметь фиксированное верхнее поле и не изменять размеры по ширине или высоте.
Затем перетащите представление изображений из библиотеки объектов в только что созданный вид контейнера. Представление изображения должно заполнять представление контейнера и иметь его маску авторазмера, чтобы все поля были фиксированными, а также для изменения ширины и высоты. Установите выходной ticketMain
владельца файла на представление изображения.
В инспекторе атрибутов выберите изображения для заглушки и основные части заявки, используя изображения, которые вы импортировали в свой проект. Убедитесь, что вы отметили «Взаимодействие с пользователем включено» как для представлений изображения, так и для представления контейнера.
Шаг 3
Отредактируйте метод init
в RedeemTicketViewController
чтобы задать заголовок панели навигации для этого экрана и инициализируйте self.isHeld
значением NO
.
1
2
3
4
5
6
7
8
9
|
— (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.navigationItem.title = @»Special Guests at Generic Venue»;
self.isHeld = NO;
}
return self;
}
|
Шаг 4
Создайте и запустите приложение, чтобы проверить, как оно выглядит.
Я включил QR-код в свои изображения, чтобы, если бы системы билетов на месте были обновлены для обработки Passbook, было бы беспрепятственно продолжать использовать это приложение после обновления.
5. Добавьте UIGestureRecognizers
Шаг 1
В viewDidLoad
( viewDidLoad
после загрузки представления из XIB) создайте UILongPressGestureRecognizer
и добавьте его в ticketStub
изображения ticketStub
. Создайте UISwipeGestureRecognizer
и добавьте его в представление изображения UISwipeGestureRecognizer
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
-(void)viewDidLoad
{
[super viewDidLoad];
self.longPressGest = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(handleLongPress:)];
self.longPressGest.minimumPressDuration = 0.1;
[self.ticketStub addGestureRecognizer:self.longPressGest];
self.swipeGest = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(handleSwipe:)];
self.swipeGest.direction = (UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight);
[self.ticketMain addGestureRecognizer:self.swipeGest];
}
|
Обратите внимание, что я установил маску направления в распознавателе жестов смахивания для обработки влево или вправо, а не вверх или вниз. Я установил минимальную продолжительность печати 0,1 секунды. Их можно настроить в вашем приложении, чтобы создать максимально реалистичное действие.
Шаг 2
Затем напишите методы для обработки длинного нажатия и прокрутки, называемые handleLongPress:
и handleSwipe:
соответственно. Метод длинного нажатия будет использоваться для определения, действительно ли жест смахивания имеет какой-либо эффект. Представьте себе физический лист бумаги. Вы можете попытаться разорвать его столько, сколько захотите, но если он не удерживается на месте, то ничего не произойдет.
handleLongPress:
вызывается всякий раз, когда изменяется состояние распознавателя жестов, например, начинается или заканчивается жест. Мы хотим проверить, закончился ли жест, т. self.isHeld
Поднял палец, и убедиться, что self.isHeld
снова установлен в NO
. Если, если жест не закончился, установите для self.isHeld
значение YES
которое мы будем использовать, чтобы смахивание выполняло свое действие.
1
2
3
4
5
6
7
8
|
— (void)handleLongPress:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
self.isHeld = NO;
} else if(!self.isHeld){
self.isHeld = YES;
}
}
|
Шаг 3
Далее давайте реализуем handleSwipe:
Мы проверяем, имеет ли self.isHeld
true, и, если это так, мы self.isHeld
представление self.isHeld
с экрана, используя переход в виде свернувшись вверх, который выглядит так, будто билет разрывается.
1
2
3
4
5
6
7
8
9
|
— (void)handleSwipe:(UIGestureRecognizer *)sender
{
if(self.isHeld){
[UIView transitionWithView:self.containerView duration:1
options:UIViewAnimationOptionTransitionCurlUp
animations:^ { [sender.view removeFromSuperview];
completion:nil];
}
}
|
Шаг 4
Создайте и запустите приложение, чтобы опробовать новые жесты. Нажмите и удерживайте квитанцию билета и одновременно проведите по другой части билета. Вы должны увидеть основную часть «оторвать» и исчезнуть.
Вывод
Мы рассмотрели создание базовой навигационной структуры для отображения списка билетов и экрана билетов, которые можно активировать, нажав одну часть экрана и проведя пальцем по другой части, создав эффект, подобный разрыву билета в реальной жизни.
Это ни в коем случае не конец пути. Нажав назад и выбрав строку из таблицы, вы вернетесь обратно к экрану с билетом, который все еще не поврежден. Вам нужно написать некоторую логику, чтобы предотвратить возможность повторной выкупки выкупленного билета.
Я также использовал изображения для представления билетов. Это сделано для того, чтобы исключить как можно больше ненужного кода для этого урока, чтобы мы могли действительно сосредоточиться на важных частях. В реальной ситуации вы бы хотели использовать поля, в которые вы могли бы динамически включать информацию о событии.
Есть несколько способов повысить безопасность и устранить случайные погашения. Вы можете записать местоположение и время устройства, когда билет был проведен. Очевидно, что если билет был выкуплен за несколько дней до мероприятия и в месте, расположенном далеко от места, можно смело предположить, что это был несчастный случай. Билет, выкупленный 5 минут назад прямо у места встречи, вероятно, кто-то удачно судит.