Краткое введение
В одном из проектов мне понадобилось определение ориентации жеста Pinch. Нужно это было для выборочного масштабирования объекта по оси. Т.е. если пальцы сдвигаются/раздвигаются вертикально, то и объект мы масштабируем только вертикально. К сожалению, стандартный распознаватель подобного жеста UIPinchGestureRecognizer не предоставляет такой информации. Просмотрев первую страницу результатов поиска в гугле я не наткнулся на готовое решение. «Ну что же, напишем сами. Так даже интересней :)» — подумал я и приступил к реализации своего пинча.
Редактирование
10.07.2010 — обновлен исходный код. Добавлено свойство oScale(CGPoint). Содержит коэффициент масштабирования по отдельным осям. Аналог scale с UIPinchGestureRecognizer.
Основная разработка
Для определения ориентации пальцев при выполнении Pinch'а нужно решить две подзадачи
- Определить сам жест Pinch
- Определить угол наклона пальцев
С первой подзадачей проблем не возникло. Прочитав еще раз документацию я удостоверился в том, что
Решение второй подзадачи тоже не заняло много времени. Немного подумав

я нашел решение: просто определять угол между осью Oy экрана и линией, проходящей через пальцы пользователя.
Все, можно было приниматься за написание кода. Ниже результаты работы:
Описание:
//
// UIOrientedPinchGestureRecognizer.h
// Бойко А.В.
//
#import <Foundation/Foundation.h>
#import "UIKit/UIGestureRecognizerSubclass.h"
typedef enum{
OrientationNone = 0,
OrientationVertical = 1,
OrientationHorizontal = 2,
OrientationDiagonal = 3,
OrientationInvertedDiagonal = 4
}Orientation;
@interface UIOrientedPinchGestureRecognizer : UIPinchGestureRecognizer {
Orientation _orientation;
CGFloat _angle;
CGPoint _lastScale;
CGFloat _oldScale;
}
//Ориентация жеста
@property (nonatomic) Orientation orientation;
//Угол жеста, относительно OY. В радианах. От 0 до PI. При angle==PI_2 orientation==OrientationHorizontal
@property (nonatomic) CGFloat angle;
@property (nonatomic) CGPoint oScale;
@end
#pragma mark c_func
static inline CGFloat degToRad(CGFloat deg) {return deg*M_PI/180;}
static inline CGFloat radToDeg(CGFloat rad) {return rad*180/M_PI;}
//определение косинуса угла между тремя точками. Определяемый угол в точке A, т.е это угол PointB-PointA-PointC
static inline double getCosAngle(CGPoint pointA,CGPoint pointB, CGPoint pointC){
CGPoint vectorAB = CGPointMake(pointB.x-pointA.x, pointB.y-pointA.y);
CGPoint vectorAC = CGPointMake(pointC.x-pointA.x, pointC.y-pointA.y);
double dAB=sqrt(vectorAB.x*vectorAB.x+vectorAB.y*vectorAB.y);
double dAC=sqrt(vectorAC.x*vectorAC.x+vectorAC.y*vectorAC.y);
double scal = vectorAB.x * vectorAC.x +vectorAB.y * vectorAC.y;
double angle = scal/(dAB*dAC);
return angle;
}
//определение угла между тремя точками. Определяемый угол в точке A, т.е это угол PointB-PointA-PointC
static inline double getAngle(CGPoint pointA,CGPoint pointB, CGPoint pointC){
if ((CGPointEqualToPoint(pointA, pointB))||(CGPointEqualToPoint(pointA, pointC))||(CGPointEqualToPoint(pointB, pointC)))
return 0; //если задано по сути не 3, а 2 точки то возвращаем ноль
return acos(getCosAngle(pointA, pointB, pointC));
}
#pragma -
Реализация:
//
// UIOrientedPinchGestureRecognizer.m
//
#import "UIOrientedPinchGestureRecognizer.h"
@implementation UIOrientedPinchGestureRecognizer
@synthesize angle=_angle, orientation=_orientation,oScale=_lastScale;
#pragma mark init/dealloc
-(void) dealloc{
[super dealloc];
}
-(id) init{
self = [super init];
if (self) {
_orientation = OrientationNone;
_angle = 0;
}
return self;
}
#pragma mark - Methods
-(void) detectAngle:(NSArray *) touches{
if ([touches count]!=2) {
_orientation= OrientationNone;
_angle = 0;
//вслучае больше двух пальцев считаем
//что все коэф. были увеличины
_lastScale.x *= self.scale/_oldScale;
_lastScale.y *= self.scale/_oldScale;
_oldScale = self.scale;
return;
}
//Получим две точки
CGPoint firstPoint =[[touches objectAtIndex:0] locationInView:self.view];
CGPoint secondPoint = [[touches objectAtIndex:1] locationInView:self.view];
//определим ближайшую к OY
if (secondPoint.x<firstPoint.x) {
CGPoint tmp = secondPoint;
secondPoint = firstPoint;
firstPoint = tmp;
}
//Найдем угол
_angle = getAngle(firstPoint,secondPoint,CGPointMake(firstPoint.x,firstPoint.y-100.0f));
//по углу определим ориентацию
if ((_angle> M_PI_4/2.0f)&&(_angle< M_PI_4+M_PI_4/2.0f)) {
_orientation= OrientationDiagonal;
_lastScale.x *= self.scale/_oldScale;
_lastScale.y *= self.scale/_oldScale;
}else
if ((_angle> M_PI_4+M_PI_4/2.0f)&&(_angle<M_PI_2+M_PI_4/2.0f)) {
_orientation = OrientationHorizontal;
_lastScale.x *= self.scale/_oldScale;
}else
if ((_angle>M_PI_2+M_PI_4/2.0f)&&(_angle<M_PI_2+M_PI_4+M_PI_4/2.0f)){
_orientation = OrientationInvertedDiagonal;
_lastScale.x *= self.scale/_oldScale;
_lastScale.y *= self.scale/_oldScale;
}else{
_orientation=OrientationVertical;
_lastScale.y *= self.scale/_oldScale;
}
_oldScale=self.scale;
return;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
_lastScale = CGPointMake(1.0f, 1.0f);
_oldScale = 1.0f;
[self detectAngle:[touches allObjects]];
[super touchesBegan:touches withEvent:event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[self detectAngle:[touches allObjects]];
[super touchesMoved:touches withEvent:event];
}
@end
Использовать так же, как и UIPinchGestureRecognizer
UIOrientedPinchGestureRecognizer *pinchGesture = [[UIOrientedPinchGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePinchGesture:)];
[pinchGesture setDelegate:self];
pinchGesture.cancelsTouchesInView=NO;
[self.view addGestureRecognizer:pinchGesture];
Обработка:
- (IBAction)handlePinchGesture:(UIOrientedPinchGestureRecognizer *)sender {
NSLog(@"angle: %f orientation: %i scale.x:%f scale.y:%f",radToDeg(sender.angle),sender.orientation,sender.oScale.x,sender.oScale.y);
}
Недостатки:
На данный момент нет определения угла для трех/четырех и т.д. пальцев.
Источники
- Картинка взята с blogiat.com
- UIPinchGestureRecognizer
Спасибо всем за внимание.