Краткое введение
В одном из проектов мне понадобилось определение ориентации жеста 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
Спасибо всем за внимание.
