iOSのスクリーンで２本指を使いUIViewの回転、拡大/縮小を同時に行う

２本指を使って、Viewの回転、拡大を同時に行うことができるサンプルです。
スタンプカメラなどに活用できるかと思います。

UIPinchGestureRecognizerとUIRotationGestureRecognizerを使用しててピンチイン/アウトもしくは回転を行うことはできますが、同時にこれら両方のジェスチャーを認識することはできないようです。

しかしながら、
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
を用いることでViewの回転、拡大を同時に行うことができます。

以下、実装方法です。
まず、UIViewを継承した変形可能なViewのクラスを作成します。

```objc:TransformableView.h
#import <UIKit/UIKit.h>

@interface TransformableView : UIView

@property(nonatomic) CGFloat scale;
@property(nonatomic) CGFloat angle;
@property(nonatomic) BOOL isTransformable;

@end
```

```objc:TransformableView.m
#import "TransformableView.h"

@implementation TransformableView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        _scale = 1.0;
        _angle = 0.0;
        _isTransformable = YES;
    }
    return self;
}

-(void)setScale:(CGFloat)scale{
    if (!_isTransformable) {
        return;
    }
    //Minimum scale
    if (scale<0.5) {
        scale = 0.5;
    }
    //Max scale
    if (scale>2.0) {
        scale = 2.0;
    }
    _scale = scale;
    [self doTransform];
}

-(void)setAngle:(CGFloat)angle{
    if (!_isTransformable) {
        return;
    }
    _angle = angle;
    [self doTransform];
}

-(void)doTransform{
    CGAffineTransform pinchTransform = CGAffineTransformMakeScale(_scale, _scale);
    CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(_angle);
    self.transform = CGAffineTransformConcat(pinchTransform, rotationTransform);
}

@end
```

スケールと角度のセッターをオーバーライドしてそれぞれから変形を行うメソッドを呼び出しています。

そして、このViewを配置するView Controllerの中に、以下の記述をします。

```objc:ViewController.m
#import "ViewController.h"
#import "TransformableView.h"

@interface ViewController (){
    TransformableView *tView;
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    
    //Multiple touch
    self.view.multipleTouchEnabled = YES;
    
    //New Transformable View
    tView = [TransformableView new];
    tView.backgroundColor = [UIColor redColor];
    tView.isTransformable = YES;
    tView.frame = CGRectMake(0, 0, 100, 100);
    tView.center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
    [self.view addSubview:tView];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    if ([touches count] == 2) {
        
        //Touches
        NSArray *touchesArray = [touches allObjects];
        UITouch *touch1 = touchesArray[0];
        UITouch *touch2 = touchesArray[1];
        
        //Point 1
        CGPoint prePoint1 = [touch1 previousLocationInView:self.view];
        CGPoint locationPoint1 = [touch1 locationInView:self.view];
        
        //Point2
        CGPoint prePoint2 = [touch2 previousLocationInView:self.view];
        CGPoint locationPoint2 = [touch2 locationInView:self.view];
        
        //Distance
        CGFloat preDistance = sqrtf(powf(prePoint2.x-prePoint1.x, 2)+
                                    powf(prePoint2.y-prePoint1.y, 2));
        CGFloat locationDistance = sqrtf(powf(locationPoint2.x-locationPoint1.x, 2)+
                                         powf(locationPoint2.y-locationPoint1.y, 2));
        
        //Scale increment
        tView.scale *= locationDistance/preDistance;
        
        //Angle increment
        CGFloat angleIncrement = angleBetweenLinesInRadians([touch1 previousLocationInView:self.view],
                                                            [touch2 previousLocationInView:self.view],
                                                            [touch1 locationInView:self.view],
                                                            [touch2 locationInView:self.view]);
        tView.angle += angleIncrement;
    }
}

static inline CGFloat angleBetweenLinesInRadians(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, 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 > line1Slope) ? degs : -degs;
}

@end
```

２本指の間の距離の変化率から、Viewのスケールの変化率を計算し、
２本指を結んだ直線の傾きの変化から、Viewの角度の変化率を計算しています。
傾きの計算方法に関しては、以下の記事を参照しました。
http://stackoverflow.com/questions/7863265/swipe-detection-in-any-angle

興味のある方は、ぜひスクリーンに２本指を置いて試してみてくださいね。
シミュレータの場合は、optionを押しながら画面をクリックすると２本指をシミュレートできます。

サンプルコードをGitHubに置いておきますね。
https://github.com/yukinaga/PinchRotationSample