iOS, UIPanGesture 可以用来做UIImageView 的角拖动调整大小吗?

iOS, can UIPanGesture be used to do UIImageView corner drag for resizing?

我已经搜索了一段时间(几天)的解决方案,但 none 确实满足了我的需要。 (iOS、Objective C、顺便说一句)。

我有一个用 UIPanGestureRecognizer 调整大小的 UIImageView。典型的平底锅工作正常。好像离我很近

但我想通过拖动图像的一个角来调整 ImageView 的大小,并且只调整与所选角相关的尺寸。如果我只执行 "handleResize" UIPanGesture 方法,效果会很好。但是,如果我捏合或旋转图像,边界或框架就会变得混乱。我想我需要某种 CGAffineTransform 但我无法让它工作。

我需要帮助指明正确的方向。我一直在使用 CGAffineTransforms,但我可能走错了路。

在我的 ViewController.h 中,我有一个浮动,touchRadius,设置为 25:

float touchRadius = 25;

我的 ViewController.m:

中有一个 UIPanGestureRecognizer
- (IBAction)handleResize:(UIPanGestureRecognizer *)recognizer {
    // where the user has touched down
    CGPoint touch = [recognizer locationInView: self.view];

    //get the translation amount in x,y
    CGPoint translation = [recognizer translationInView:self.view];

    if (recognizer.state == UIGestureRecognizerStateBegan ||
        recognizer.state == UIGestureRecognizerStateChanged) {
        CGRect frame = recognizer.view.frame;

        CGRect topLeft = CGRectMake(frame.origin.x,
                                    frame.origin.y,
                                    touchRadius, touchRadius);
        CGRect bottomLeft = CGRectMake(frame.origin.x,
                                     frame.origin.y + frame.size.height - touchRadius,
                                     touchRadius, touchRadius);
        CGRect topRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
                                     frame.origin.y,
                                     touchRadius, touchRadius);
        CGRect bottomRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
                                        frame.origin.y + frame.size.height - touchRadius,
                                        touchRadius, touchRadius);
        Boolean useNewFrame = YES;
        CGRect newFrame = frame;

        if (CGRectContainsPoint(topLeft, touch)) {
            newFrame.origin.x += translation.x;
            newFrame.origin.y += translation.y;
            newFrame.size.width -= translation.x;
            newFrame.size.height -= translation.y;
            recognizer.view.frame = newFrame;
        } else if (CGRectContainsPoint(topRight, touch)) {
            newFrame.origin.y += translation.y;
            newFrame.size.width += translation.x;
            newFrame.size.height -= translation.y;
            recognizer.view.frame = newFrame;
        } else if (CGRectContainsPoint(bottomLeft, touch)) {
            newFrame.origin.x += translation.x;
            newFrame.size.width -= translation.x;
            newFrame.size.height += translation.y;
            recognizer.view.frame = newFrame;
        } else if (CGRectContainsPoint(bottomRight, touch)) {
            newFrame.size.width += translation.x;
            newFrame.size.height += translation.y;
            recognizer.view.frame = newFrame;
        } else {
            useNewFrame = NO;
        }

        if (useNewFrame) {
            // make sure it doesn't go too small to touch
            if (newFrame.size.width < touchRadius)
                newFrame.size.width = touchRadius;
            if (newFrame.size.height < touchRadius)
                newFrame.size.height = touchRadius;
            recognizer.view.frame = newFrame;

            // I THINK I NEED A TRANSFORM HERE
        } else {
            // use the fallback translate
            [recognizer.view setTransform:CGAffineTransformTranslate(recognizer.view.transform, translation.x, translation.y)];
        }
    }
    [recognizer setTranslation:CGPointZero inView:self.view];
}

- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer
{   
    if (recognizer.state == UIGestureRecognizerStateBegan ||
        recognizer.state == UIGestureRecognizerStateChanged)
    {
        // make sure it stays visible
        float scale = recognizer.scale;

        if (recognizer.view.frame.size.width * scale > touchRadius * 2 ||
            recognizer.view.frame.size.height * scale > touchRadius * 2) {
            [recognizer.view setTransform:CGAffineTransformScale(recognizer.view.transform, scale, scale)];
            recognizer.scale = 1;
        }
    }
}

- (void)handleRotate:(UIRotationGestureRecognizer *)recognizer {
    UIGestureRecognizerState state = [recognizer state];

    if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
    {
        CGFloat rotation = [recognizer rotation];
        [recognizer.view setTransform:CGAffineTransformRotate(recognizer.view.transform, rotation)];

    }
    [recognizer setRotation:0];
}

修改视图框架后是否需要转换?或者我应该走另一条路?

我正在尝试在不使用其他人的库的情况下以本机方式执行此操作,所以我理解它。这个简单的示例将成为更大项目的一部分。

经过一些研究,我想出了如何修改 corner/side 拖动平移的变换,以调整图像大小。以下是使其发挥作用的关键要素:

  • 在每个手势(平移、捏合、旋转)中保存 CGAffineTransform,如果 识别器状态 = UIGestureRecognizerStateBegan
  • 在每个手势(平移、捏合、旋转)中对保存的初始变换应用新的 CGAAffineTransforms
  • 保存corner/side检测到的UIGestureRecognizerStateBegan状态
  • 清除在 UIGestureRecognizerStateEnded 状态下检测到的corner/side
  • 对于平移手势,根据 corner/side 检测到的
  • 调整翻译 x/y 值
  • 确保触摸半径足够大以便有用(24 太小,48 很好用)

转换是这样工作的:

// pan the image
recognizer.view.transform = CGAffineTransformTranslate(initialTransform, tx, ty);

if (scaleIt) {
    // the origin or size changed
    recognizer.view.frame = newFrame;
}

txty 值是识别器返回的默认值,如果平移是从图像的中心。但是,如果用户触摸靠近视图框架的角落或一侧,则 tx/tyframe origin 会调整以调整大小视图使它看起来好像那个角或边被拖动以调整视图大小。

例如:

CGRect newFrame = recognizer.view.frame;

if (currentDragType == DRAG_TOPLEFT) {
    tx = -translation.x;
    ty = -translation.y;
    newFrame.origin.x += translation.x;
    newFrame.origin.y += translation.y;
    newFrame.size.width -= translation.x;
    newFrame.size.height -= translation.y;
} else if (currentDragType == DRAG_TOPRIGHT) {
    tx = translation.x;
    ty = -translation.y;
    newFrame.origin.y += translation.y;
    newFrame.size.width += translation.x;
    newFrame.size.height -= translation.y;
}

这使得左上角或右上角根据触摸移动的距离进出。与整个视图随触摸一起移动的中心平移不同,对角(或边)保持固定。

有 2 个问题我没有解决:

  • 如果图像旋转明显,corner/side检测(平移)不起作用,因为我没有检查旋转坐标系中的触摸(但中心平移仍然可以正常工作)
  • 旋转图像后,捏合手势会不稳定,并且可以将图像调整为零,使其不可见

我创建了一个简单的演示并将其上传到 GitHub:https://github.com/ByteSlinger/ImageGestureDemo

是的,我知道 link 总有一天会消失,所以这是代码:

ViewController.h

//
//  ViewController.h
//  ImageGestureDemo
//
//  Created by ByteSlinger on 6/21/18.
//  Copyright © 2018 ByteSlinger. All rights reserved.
//

#import <UIKit/UIKit.h>
NSString *APP_TITLE = @"Image Gesture Demo";
NSString *INTRO_ALERT = @"\nDrag, Pinch and Rotate the Image!"
                        "\n\nYou can also Drag, Pinch and Rotate the background image."
                        "\n\nDouble tap an image to reset it";

float touchRadius = 48;   // max distance from corners to touch point

typedef NS_ENUM(NSInteger, DragType) {
    DRAG_OFF,
    DRAG_ON,
    DRAG_CENTER,
    DRAG_TOP,
    DRAG_BOTTOM,
    DRAG_LEFT,
    DRAG_RIGHT,
    DRAG_TOPLEFT,
    DRAG_TOPRIGHT,
    DRAG_BOTTOMLEFT,
    DRAG_BOTTOMRIGHT
};

@interface ViewController : UIViewController <UIGestureRecognizerDelegate>

//callback to process gesture events
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer;
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer;
- (IBAction)handleRotate:(UIRotationGestureRecognizer *)recognizer;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
@end

ViewController.m

//
//  ViewController.m
//  ImageGestureDemo
//
//  This is a DEMO. It shows how to pan, pinch, rotate and drag/resize a UIImageView.
//
//  There is a background image and a foreground image.  Both images can be
//  panned, pinched and rotated, but only the foreground image can be resized
//  by dragging one of it's corners or it's sides.
//
//  NOTE:  Sure, much of this code could have been put into a subclass of UIView
//         or UIImageView.  But for simplicity and reference sake, all code and
//         methods are in one place, this ViewController subclass.  There is no
//         error checking at all.  App tested on an iPhone 6+ and an iPad gen3.
//
//  Features:
//      - allows an image to be resized with pan gesture by dragging corners and sides
//      - background image can be modified (pan, pinch, rotate)
//      - foreground image can be modified (pan, pinch, rotate, drag/resize)
//      - all image manipulation done within gestures linked from storyboard
//      - all finger touches on screen show with yellow circles
//      - when dragging, the touch circles turn to red (so you know when gestures start)
//      - double tap on foreground image resets it to original size and rotation
//      - double tap on background resets it and also resets the foreground image
//      - screen and image touch and size info displayed on screen
//      - uses CGAffineTransform objects for image manipulation
//      - uses UIGestureRecognizerStateBegan in gestures to save transforms (the secret sauce...)
//
//  Known Issues:
//      - when the image is rotated, determining if a touch is on a corner or side
//        does not work for large rotations.  Need to check touch points against
//        non rotated view frame and adjust accordingly.
//      - after rotations, pinch and resize can shrink image to invisibility despite
//        code attempts to prevent it.
//
//  Created by ByteSlinger on 6/21/18.
//  Copyright © 2018 ByteSlinger. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) IBOutlet UIImageView *backgroundImageView;
@property (strong, nonatomic) IBOutlet UIImageView *foregroundImageView;
@property (strong, nonatomic) IBOutlet UILabel *screenInfoLabel;
@property (strong, nonatomic) IBOutlet UILabel *touchInfoLabel;
@property (strong, nonatomic) IBOutlet UILabel *imageInfoLabel;
@property (strong, nonatomic) IBOutlet UILabel *backgroundInfoLabel;
@property (strong, nonatomic) IBOutlet UILabel *changeInfoLabel;
@property (strong, nonatomic) IBOutlet UITapGestureRecognizer *backgroundTapGesture;
@property (strong, nonatomic) IBOutlet UITapGestureRecognizer *foregroundTapGesture;
@end

@implementation ViewController
CGRect originalImageFrame;
CGRect originalBackgroundFrame;
CGAffineTransform originalImageTransform;
CGAffineTransform originalBackgroundTransform;
NSMutableArray* touchCircles = nil;
DragType currentDragType = DRAG_OFF;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // set this to whatever your desired touch radius is
    touchRadius = 48;

    // In Storyboard this must have set to 1, then this seems to work ok
    // when setting the double tap here
    _foregroundTapGesture.numberOfTapsRequired = 2;
    _backgroundTapGesture.numberOfTapsRequired = 2;

    [self centerImageView:_foregroundImageView];

    originalImageFrame = _foregroundImageView.frame;
    originalBackgroundFrame = _backgroundImageView.frame;
    originalImageTransform = _foregroundImageView.transform;
    originalBackgroundTransform = _backgroundImageView.transform;

    _backgroundImageView.contentMode = UIViewContentModeCenter;
    _foregroundImageView.contentMode = UIViewContentModeScaleToFill;    // allow stretch
    [_backgroundImageView setUserInteractionEnabled:YES];
    [_backgroundImageView setMultipleTouchEnabled:YES];
    [_foregroundImageView setUserInteractionEnabled:YES];
    [_foregroundImageView setMultipleTouchEnabled:YES];

    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter]
     addObserver:self selector:@selector(orientationChanged:)
     name:UIDeviceOrientationDidChangeNotification
     object:[UIDevice currentDevice]];

    [_touchInfoLabel setText:nil];
    [_changeInfoLabel setText:nil];
    [_imageInfoLabel setText:nil];
    [_backgroundInfoLabel setText:nil];

    touchCircles = [[NSMutableArray alloc] init];
}

- (void)viewDidAppear:(BOOL)animated {
    [self alert:APP_TITLE :INTRO_ALERT];
}

- (void) orientationChanged:(NSNotification *)note
{
    UIDevice * device = note.object;
    switch(device.orientation)
    {
        case UIDeviceOrientationPortrait:
            /* start special animation */
            break;

        case UIDeviceOrientationPortraitUpsideDown:
            /* start special animation */
            break;

        default:
            break;
    };

    [_screenInfoLabel setText:[NSString stringWithFormat:@"Screen: %.0f/%.0f",
                              self.view.frame.size.width,self.view.frame.size.height]];
}

//
// Update the info labels from the passed objects
//
- (void) updateInfo:(UIView *)imageView touch:(CGPoint)touch change:(CGPoint)change {
    NSString *label;
    UILabel *infoLabel;

    if (imageView == _foregroundImageView) {
        label = @"Image: %0.f/%0.f, %0.f/%0.f";
        infoLabel = _imageInfoLabel;
    } else {
        label = @"Background: %0.f/%0.f, %0.f/%0.f";
        infoLabel = _backgroundInfoLabel;
    }

    [infoLabel setText:[NSString stringWithFormat:label,
                              imageView.layer.frame.origin.x,
                              imageView.layer.frame.origin.y,
                              imageView.layer.frame.size.width,
                              imageView.layer.frame.size.height]];

    [_touchInfoLabel setText:[NSString stringWithFormat:@"Touch: %0.f/%.0f",
                              touch.x,touch.y]];

    [_changeInfoLabel setText:[NSString stringWithFormat:@"Change: %0.f/%.0f",
                               change.x,change.y]];
}

//
// Center the passed image frame within it's bounds
//
- (void)centerImageView:(UIImageView *)imageView {
    CGSize boundsSize = self.view.bounds.size;
    CGRect frameToCenter = imageView.frame;

    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.origin.x = 0;

    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.origin.y = 0;

    imageView.frame = frameToCenter;
}

//
// Remove all touch circles
//
- (void)removeTouchCircles {
    [touchCircles makeObjectsPerformSelector: @selector(removeFromSuperview)];
    [touchCircles removeAllObjects];
}

//
// Draw a circle around the passed point where the user has touched the screen
//
- (void)drawTouchCircle:(UIView *)view fromCenter:(CGPoint)point ofRadius:(float)radius {
    CGRect frame = CGRectMake(point.x - view.frame.origin.x - radius,
                              point.y - view.frame.origin.y - radius,
                              radius * 2, radius * 2);

    UIView *circle = [[UIView alloc] initWithFrame:frame];
    circle.alpha = 0.5;
    circle.layer.cornerRadius = radius;
    circle.backgroundColor = currentDragType == DRAG_OFF ? [UIColor yellowColor] : [UIColor redColor];
    [circle.layer setBorderWidth:1.0];
    [circle.layer setBorderColor:[[UIColor blackColor]CGColor]];

    [view addSubview:circle];

    [touchCircles addObject:circle];
}

//
// Draw a touch circle for the passed user touch
//
- (void)handleTouchEvent:(UIView *) view
                 atPoint:(CGPoint) point
                forState:(UIGestureRecognizerState) state
                   clear:(Boolean) clear {
    //NSLog(@"handleTouchEvent");
    if (clear) {
        [self removeTouchCircles];
    }

    if (state == UIGestureRecognizerStateEnded) {
        [self removeTouchCircles];
    } else {
        [self drawTouchCircle:self.view fromCenter:point ofRadius:touchRadius];
    }

    [_touchInfoLabel setText:[NSString stringWithFormat:@"Touch: %0.f/%.0f",
                              point.x,point.y]];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    //NSLog(@"touchesBegan");
    [self removeTouchCircles];

    NSSet *allTouches = [event allTouches];
    NSArray *allObjects = [allTouches allObjects];
    for (int i = 0;i < [allObjects count];i++)
    {
        UITouch *touch = [allObjects objectAtIndex:i];
        CGPoint location = [touch locationInView: self.view];
        [self handleTouchEvent:touch.view atPoint:location forState:UIGestureRecognizerStateBegan clear:NO];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    //NSLog(@"touchesMoved");
    [self removeTouchCircles];

    NSSet *allTouches = [event allTouches];
    NSArray *allObjects = [allTouches allObjects];
    for (int i = 0;i < [allObjects count];i++)
    {
        UITouch *touch = [allObjects objectAtIndex:i];
        CGPoint location = [touch locationInView: self.view];
        [self handleTouchEvent:touch.view atPoint:location forState:UIGestureRecognizerStateChanged clear:NO];
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    //NSLog(@"touchesEnded");
    [self removeTouchCircles];
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //NSLog(@"touchesCancelled");
    [self removeTouchCircles];
}

//
// Double tap resets passed image.  If background image, also reset foreground image.
//
- (IBAction)handleDoubleTap:(UITapGestureRecognizer *)recognizer {
    CGPoint touch = [recognizer locationInView: self.view];

    if (recognizer.state == UIGestureRecognizerStateBegan ||
        recognizer.state == UIGestureRecognizerStateChanged) {
        [self handleTouchEvent:recognizer.view atPoint:touch forState:recognizer.state clear:NO];
    } else {
        [self removeTouchCircles];
    }
    [self alert:@"Reset" :@"The Image has been Reset!"];

    CGRect frame = originalImageFrame;
    CGAffineTransform transform = originalImageTransform;

    if (recognizer.view == _backgroundImageView) {
        _foregroundImageView.transform = transform;
        _foregroundImageView.frame = frame;
        [self updateInfo:_foregroundImageView touch:touch change:CGPointZero];

        frame = originalBackgroundFrame;
        transform = originalBackgroundTransform;
    }

    recognizer.view.transform = transform;
    recognizer.view.frame = frame;
    [self updateInfo:recognizer.view touch:touch change:CGPointZero];
}

- (void) setDragType:(CGRect)frame withTouch:(CGPoint)touch {
    // the corners and sides of the current view frame
    CGRect topLeft = CGRectMake(frame.origin.x,frame.origin.y,
                                touchRadius, touchRadius);
    CGRect bottomLeft = CGRectMake(frame.origin.x,
                                   frame.origin.y + frame.size.height - touchRadius,
                                   touchRadius, touchRadius);
    CGRect topRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
                                 frame.origin.y,
                                 touchRadius, touchRadius);
    CGRect bottomRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
                                    frame.origin.y + frame.size.height - touchRadius,
                                    touchRadius, touchRadius);
    CGRect leftSide = CGRectMake(frame.origin.x,frame.origin.y,
                                 touchRadius, frame.size.height);
    CGRect rightSide = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
                                  frame.origin.y,
                                  touchRadius, frame.size.height);
    CGRect topSide = CGRectMake(frame.origin.x,frame.origin.y,
                                frame.size.width, touchRadius);
    CGRect bottomSide = CGRectMake(frame.origin.x,
                                   frame.origin.y + frame.size.height - touchRadius,
                                   frame.size.width, touchRadius);

    if (CGRectContainsPoint(topLeft, touch)) {
        currentDragType = DRAG_TOPLEFT;
    } else if (CGRectContainsPoint(topRight, touch)) {
        currentDragType = DRAG_TOPRIGHT;
    } else if (CGRectContainsPoint(bottomLeft, touch)) {
        currentDragType = DRAG_BOTTOMLEFT;
    } else if (CGRectContainsPoint(bottomRight, touch)) {
        currentDragType = DRAG_BOTTOMRIGHT;
    } else if (CGRectContainsPoint(topSide, touch)) {
        currentDragType = DRAG_TOP;
    } else if (CGRectContainsPoint(bottomSide, touch)) {
        currentDragType = DRAG_BOTTOM;
    } else if (CGRectContainsPoint(leftSide, touch)) {
        currentDragType = DRAG_LEFT;
    } else if (CGRectContainsPoint(rightSide, touch)) {
        currentDragType = DRAG_RIGHT;
    } else if (CGRectContainsPoint(frame, touch)) {
        currentDragType = DRAG_CENTER;
    } else {
        currentDragType = DRAG_OFF; // touch point is not in the view frame
    }
}

//
// Return the unrotated size of the view
//
- (CGSize) getActualSize:(UIView *)view {
    CGSize result;
    //CGSize originalSize = view.frame.size;
    CGAffineTransform originalTransform = view.transform;
    float rotation = atan2f(view.transform.b, view.transform.a);

    // reverse rotation of current transform
    CGAffineTransform unrotated = CGAffineTransformRotate(view.transform, -rotation);

    view.transform = unrotated;

    // get the size of the "unrotated" view
    result = view.frame.size;

    // reset back to what it was
    view.transform = originalTransform;

    //NSLog(@"Size current = %0.f/%0.f, rotation = %0.2f, unrotated = %0.f/%0.f",
    //      originalSize.width,originalSize.height,
    //      rotation,
    //      result.width,result.height);

    return result;
}

//
// Resize or Pan an image on the ViewController View
//
- (IBAction)handleResize:(UIPanGestureRecognizer *)recognizer {
    static CGRect initialFrame;
    static CGAffineTransform initialTransform;
    static Boolean scaleIt = YES;

    // where the user has touched down
    CGPoint touch = [recognizer locationInView: self.view];

    //get the translation amount in x,y
    CGPoint translation = [recognizer translationInView:recognizer.view];

    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
        initialFrame = recognizer.view.frame;
        initialTransform = recognizer.view.transform;
        [self setDragType:recognizer.view.frame withTouch:touch];
        scaleIt = YES;
    }
    if (recognizer.state == UIGestureRecognizerStateEnded) {
        currentDragType = DRAG_OFF;
        scaleIt = NO;

        [self getActualSize:recognizer.view];
    } else { 
        // our new view frame - start with the initial one
        CGRect newFrame = initialFrame;

        // adjust the translation point according to where the user touched the image
        float tx = translation.x;
        float ty = translation.y;

        // resize by dragging a corner or a side
        if (currentDragType == DRAG_TOPLEFT) {
            tx = -translation.x;
            ty = -translation.y;
            newFrame.origin.x += translation.x;
            newFrame.origin.y += translation.y;
            newFrame.size.width -= translation.x;
            newFrame.size.height -= translation.y;
        } else if (currentDragType == DRAG_TOPRIGHT) {
            ty = -translation.y;
            newFrame.origin.y += translation.y;
            newFrame.size.width += translation.x;
            newFrame.size.height -= translation.y;
        } else if (currentDragType == DRAG_BOTTOMLEFT) {
            tx = -translation.x;
            newFrame.origin.x += translation.x;
            newFrame.size.width -= translation.x;
            newFrame.size.height += translation.y;
        } else if (currentDragType == DRAG_BOTTOMRIGHT) {
            // origin does not change
            newFrame.size.width += translation.x;
            newFrame.size.height += translation.y;
        } else if (currentDragType == DRAG_TOP) {
            tx = 0;
            newFrame.origin.y += translation.y;
            newFrame.size.height -= translation.y;
        } else if (currentDragType == DRAG_BOTTOM) {
            tx = 0;
            newFrame.size.height += translation.y;
        } else if (currentDragType == DRAG_LEFT) {
            tx = -translation.x;
            ty = 0;
            newFrame.origin.x += translation.x;
            newFrame.size.width -= translation.x;
        } else if (currentDragType == DRAG_BOTTOM) {
            ty = 0;
            newFrame.size.width += translation.x;
        } else { //if (currentDragType == DRAG_CENTER) {
            newFrame.origin.x += translation.x;
            newFrame.origin.y += translation.y;
            scaleIt = NO;   // normal pan
        }

        // get the unrotated size of the view
        CGSize actualSize = [self getActualSize:recognizer.view];

        // make sure we can still touch the image
        if (actualSize.width < touchRadius * 2) {
            newFrame.size.width += touchRadius * 2;
            tx = 0; // stop resizing
        }
        if (actualSize.height < touchRadius * 2) {
            newFrame.size.height += touchRadius * 2;
            ty = 0; // stop resizing
        }

        // pan the image
        recognizer.view.transform = CGAffineTransformTranslate(initialTransform, tx, ty);

        if (scaleIt) {
            // the origin or size changed
            recognizer.view.frame = newFrame;
        }
    }

    [self updateInfo:recognizer.view touch:touch change:translation];
}

//
// Pan an image on the ViewController View
//
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
    static CGAffineTransform initialTransform;

    // where the user has touched down
    CGPoint touch = [recognizer locationInView: self.view];

    //get the translation amount in x,y
    CGPoint translation = [recognizer translationInView:recognizer.view];

    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
        initialTransform = recognizer.view.transform;
        currentDragType = DRAG_ON;
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
        currentDragType = DRAG_OFF;
        [self getActualSize:recognizer.view];
    }
    recognizer.view.transform = CGAffineTransformTranslate(initialTransform, translation.x, translation.y);

    [self updateInfo:recognizer.view touch:touch change:translation];
}

//
// Pinch (resize) an image on the ViewController View
//
- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer {
    static CGSize initialSize;
    static CGAffineTransform initialTransform;

    // where the user has touched down
    CGPoint touch = [recognizer locationInView: self.view];

    //get the translation amount in x,y
    CGPoint change = CGPointMake(recognizer.view.transform.tx, recognizer.view.transform.ty);

    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
        initialSize = recognizer.view.frame.size;
        initialTransform = recognizer.view.transform;
        currentDragType = DRAG_ON;
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
        currentDragType = DRAG_OFF;
        [self getActualSize:recognizer.view];
    }

    // make sure it stays visible
    float scale = recognizer.scale;
    float newWidth = initialSize.width * scale;
    float newHeight = initialSize.height * scale;

    // make sure we can still touch it
    if (newWidth > touchRadius * 2 && newHeight > touchRadius * 2) {
        // scale the image
        recognizer.view.transform = CGAffineTransformScale(initialTransform, scale, scale);
    }

    [self updateInfo:recognizer.view touch:touch change:change];
}

//
// Rotate an image on the ViewController View
//
- (IBAction)handleRotate:(UIRotationGestureRecognizer *)recognizer {
    static CGFloat initialRotation;
    static CGAffineTransform initialTransform;

    // where the user has touched down
    CGPoint touch = [recognizer locationInView: self.view];

    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
        initialTransform = recognizer.view.transform;
        initialRotation = atan2f(recognizer.view.transform.b, recognizer.view.transform.a);
        currentDragType = DRAG_ON;
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
        currentDragType = DRAG_OFF;
        [self getActualSize:recognizer.view];
    }

    recognizer.view.transform = CGAffineTransformRotate(initialTransform, recognizer.rotation);

    [self updateInfo:recognizer.view touch:touch change:CGPointMake(initialRotation, recognizer.rotation)];
}

//
// Prevent simultaneous gestures so my transforms don't get funky
// (may not be necessary ... )
//
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return NO;
}

//
// Spew a message to the user
//
- (void)alert:(NSString *) title :(NSString *)message {
    UIAlertController *alert = [UIAlertController
                                 alertControllerWithTitle: title
                                 message: message
                                 preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *okButton = [UIAlertAction
                                actionWithTitle:@"Ok"
                                style:UIAlertActionStyleDefault
                                handler:^(UIAlertAction * action) {
                                }];

    [alert addAction:okButton];

    [self presentViewController:alert animated:YES completion:nil];
}

@end