ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C Часть 1

4. Касания и все, что с ними связано

Обработка касаний

Последнее время сайт пополняется разнообразными уроками работы с элементами интерфейса, парсерами, кастомными элементами. Совершенно забыл о том, что мы пишем программы для телефона с сенсорным экраном. В этом уроке я покажу как «отлавливать» события касания. Для начала создадим проект на основе шаблона Single View Application и назовем его Touches.

touchesBegan

В реализацию класса ViewController добавим один единственный метод touchesBegan. Этот метод вызывается в момент создания контакта (касание пальца с экраном).

<code data-result="[object Object]">- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{
    NSLog(@"touchesBegan");

    NSSet *allTouches = [event allTouches];
    NSLog(@"Количество контактов: %i", allTouches.count);

    UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
    NSLog(@"Количество касаний: %i", touch.tapCount);

    CGPoint touchLocation = [touch locationInView:self.view];
    NSLog(@"x=%f y=%f", touchLocation.x, touchLocation.y);
    NSLog(@"%@", touch.view);
}</code>

Теперь разберемя, что в этом методе происходит. В первую очередь, я вывел в консоль имя метода. Дело в том, что touchesBegan — не единственный метод для обработки касаний и для того, чтобы понять с какого метода данные попали в консоль я вывожу имя этого метода. В этот метод нам передается объект event, с него мы будем получать информацию о касанях. Конкретно в этом случае я воспользовался методом allTouches, который возвращает объект типа NSSet, а с этого объекта выводим в консоль количество контактов с экраном (сколько пальцев одновременно каснулись экрана). У объекта allTouches существует метод allObjects, который возвращает массив контактов. То есть, для каждого контакта (пальца) свой объект UITouch. Для примера я взял с этого массива первый элемент — это тот объект, который создался при первом контакте (самый первый палец, который каснулся экрана). И для него я вывожу в консоль количество касаний (если тапнуть по экрану два раза, подобно тому как это делается для открытия папки левой кнопкой мышки — в консоль выведется цифра два). Кроме этого, у нас есть возможность получить координаты касания. Для этого следует воспользоваться методом locationInView, в который мы передаем тот UIView, для которого хотим получить эти самые координаты. Наш проект создан с шаблона Single View Application, а значит — в нем только один UIView, который я и передаю для получения координат. В случае, если вы хотите обрабатывать событие касания для какого-то конкретного элемента интерфейса — вам следует воспользоваться свойством viewкласса UITouch, в котором харнится ссылка на тот объект, в котором касание создалось. В этом примере я просто вывожу ионформацию об этом view.

touchesMoved

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

touchesEnded

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

touchesCancelled

Это самый интересный метод. Он вызывается в случае, если контакт разорван не по желанию пользователя. То есть разрыв касания вызвало какое-то системное событие (такое как нехватка памяти).

Посмотрим как все это выглядит на практике. Изменим интерфейс класса ViewController:

<code data-result="[object Object]">#import &lt;UIKit/UIKit.h&gt;

@interface ViewController : UIViewController

@property (strong, nonatomic) IBOutlet UILabel *square;
@property (strong, nonatomic) IBOutlet UILabel *touchesBeganX;
@property (strong, nonatomic) IBOutlet UILabel *touchesBeganY;
@property (strong, nonatomic) IBOutlet UILabel *touchesMovedX;
@property (strong, nonatomic) IBOutlet UILabel *touchesMovedY;
@property (strong, nonatomic) IBOutlet UILabel *touchesEndedX;
@property (strong, nonatomic) IBOutlet UILabel *touchesEndedY;
@property (strong, nonatomic) IBOutlet UILabel *contactCount;
@property (strong, nonatomic) IBOutlet UILabel *touchesCount;

@end</code>

И, соответственно, его реализацию:

 

<code data-result="[object Object]">#import "ViewController.h"

@implementation ViewController

@synthesize square;
@synthesize touchesBeganX;
@synthesize touchesBeganY;
@synthesize touchesMovedX;
@synthesize touchesMovedY;
@synthesize touchesEndedX;
@synthesize touchesEndedY;
@synthesize contactCount;
@synthesize touchesCount;

- (void)viewDidUnload
{
    [super viewDidUnload];
    self.square = nil;
    self.touchesBeganX = nil;
    self.touchesBeganY = nil;
    self.touchesMovedX = nil;
    self.touchesMovedY = nil;
    self.touchesEndedX = nil;
    self.touchesEndedY = nil;
    self.contactCount = nil;
    self.touchesCount = nil;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{
    NSSet *allTouches = [event allTouches];
    contactCount.text = [NSString stringWithFormat:@"%i", allTouches.count];

    UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
    touchesCount.text = [NSString stringWithFormat:@"%i", touch.tapCount];

    CGPoint touchLocation = [touch locationInView:self.view];

    touchesBeganX.text = [NSString stringWithFormat:@"%.0f", touchLocation.x];
    touchesBeganY.text = [NSString stringWithFormat:@"%.0f", touchLocation.y];

    if (touch.view == square) {
        square.center = touchLocation;
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSSet *allTouches = [event allTouches];
    UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
    CGPoint touchLocation = [touch locationInView:self.view];

    touchesMovedX.text = [NSString stringWithFormat:@"%.0f", touchLocation.x];
    touchesMovedY.text = [NSString stringWithFormat:@"%.0f", touchLocation.y];

    if (touch.view == square) {
        square.center = touchLocation;
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSSet *allTouches = [event allTouches];
    UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
    CGPoint touchLocation = [touch locationInView:self.view];

    touchesEndedX.text = [NSString stringWithFormat:@"%.0f", touchLocation.x];
    touchesEndedY.text = [NSString stringWithFormat:@"%.0f", touchLocation.y];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"touchesCancelled");
}

@end</code>

В общем, мы просто добавили еще один UILabel, который назвали square. А в методы touchesBegan и touchesMoved добавили условие. Если view на который пришлось касание равен нашему square — то меняем координаты последнего. Теперь добавьте в ViewController.xibобъект UILabel и свяжите его с объектом кода square. Для тех, кто забыл как это делается — можно почитать здесь. Обращаю ваше внимание, на одну очень важную деталь. У каждого объекта интерфейса есть свойство userInteractionEnabled, если ему будет установлено значение NO — то касания не будут восприниматься этим объектом. Они будут переданы ниже по иерархии объектов интерфейса. И напротив, если у объекта вышеупомянутому свойству установлено значение YES — объет будет «перехватывать» все касания и они не дойдут до к объектам интерфейса нижних уровней иерархии. Это свойство можно установить с помощью кода или в конструкторе интерфейса:

 

Установите значение User Interaction Enabled для square как показано на картинке. Если все сделано правильно — то при нажати на любую часть квадрата — его центр будет перемещаться к точке касания. Это же действие произойдет при перемещении пальца по экрну.

 

Кроме square я добавил надписи, чтобы данные о касанях выводить не в консоль, а наглядно на экран. Результат работы вы можете увидеть на первой картинке.

 

Для реализации вращения красной надписи добавим в проект метод, который будет по двум точкам расчитывать угол поворота (не забудьте объявить его в интерфейсе класса):

 

<code data-result="[object Object]">- (CGFloat)angleBetweenLinesInRadians:(CGPoint)line1Start 
                             line1End:(CGPoint)line1End 
                           line2Start:(CGPoint)line2Start 
                             line2End:(CGPoint)line2End 
{
    CGFloat a = line1End.x - line1Start.x;
	CGFloat b = line1End.y - line1Start.y;
	CGFloat c = line2End.x - line2Start.x;
	CGFloat d = line2End.y - line2Start.y;

    CGFloat line1Slope = (line1End.y - line1Start.y) / (line1End.x - line1Start.x);
    CGFloat line2Slope = (line2End.y - line2Start.y) / (line2End.x - line2Start.x);

	CGFloat degs = acosf(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));

	return (line2Slope &gt; line1Slope) ? degs : -degs;    
}</code>

 

И воспользуемся этим методом в touchesMoved:

 

<code data-result="[object Object]">- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSSet *allTouches = [event allTouches];
    UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
    CGPoint touchLocation = [touch locationInView:self.view];

    touchesMovedX.text = [NSString stringWithFormat:@"%.0f", touchLocation.x];
    touchesMovedY.text = [NSString stringWithFormat:@"%.0f", touchLocation.y];

    if (allTouches.count == 1 &amp;&amp; touch.view == square) {
        square.center = touchLocation;
    } else if (allTouches.count == 2) {
        NSArray *twoTouches = [allTouches allObjects];
        UITouch *first = [twoTouches objectAtIndex:0];
        UITouch *second = [twoTouches objectAtIndex:1];

        CGFloat currentAngle = [self angleBetweenLinesInRadians:[first previousLocationInView:self.view] 
                                                       line1End:[second previousLocationInView:self.view] 
                                                     line2Start:[first locationInView:self.view] 
                                                       line2End:[second locationInView:self.view]];

        square.transform = CGAffineTransformRotate(square.transform, currentAngle);
    }
}</code>

Теперь, если контактов два — мы подсчитываем угол прямой между этими контактами и с помощью метода CGAffineTransformRotateвращаем надпись.

Исходный код проекта можно скачать здесь.

Comments are closed.