Drag an Image within the Bounds of Superview

This post demonstrates how to drag an image on screen, respecting the bounds of the images superview.

The first step is to create a class that inherits from UIImageView, I’ll name the class ImageToDrag. There is only one instance variable in this class, currentPoint – a CGPoint structure which has an X and Y coordinate indicating where the touch began prior to dragging.

Here is the interface definition for ImageToDrag:

@interface ImageToDrag : UIImageView
{
  CGPoint currentPoint;
}
@end

When creating an instance of ImageToDrag, I override initWithImage, and within this method set the userInteractionEnabled property to YES to enable capturing of user events.

- (id)initWithImage:(UIImage *)image
{
  if (self = [super initWithImage:image])
    self.userInteractionEnabled = YES;
  return self;
}
Managing Touch Events

When a touch is detected, the first step is to store the location in the currentPoint instance variable, I do this in the method shown below:

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
  // When a touch starts, get the current location in the view
  currentPoint = [[touches anyObject] locationInView:self];
}

The next bit of code is where I manage moving of the image on screen – the basic premise is to move the images center point based on the current touch location.

touchesMoved begins by getting the current location. The next step is to determine where the touch is now versus currentPoint stored previously. I finish by setting the new image center after verifying, and adjusting as needed, to ensure the image is within the bounds of its superview:

- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
  // Get active location upon move
  CGPoint activePoint = [[touches anyObject] locationInView:self];
 
  // Determine new point based on where the touch is now located
  CGPoint newPoint = CGPointMake(self.center.x + (activePoint.x - currentPoint.x),
                                 self.center.y + (activePoint.y - currentPoint.y));
 
  //--------------------------------------------------------
  // Make sure we stay within the bounds of the parent view
  //--------------------------------------------------------
  float midPointX = CGRectGetMidX(self.bounds);
  // If too far right...
  if (newPoint.x > self.superview.bounds.size.width  - midPointX)
    newPoint.x = self.superview.bounds.size.width - midPointX;
  else if (newPoint.x < midPointX)  // If too far left...
    newPoint.x = midPointX;
 
  float midPointY = CGRectGetMidY(self.bounds);
  // If too far down...
  if (newPoint.y > self.superview.bounds.size.height  - midPointY)
    newPoint.y = self.superview.bounds.size.height - midPointY;
  else if (newPoint.y < midPointY)  // If too far up...
    newPoint.y = midPointY;
 
  // Set new center location
  self.center = newPoint;
}
Creating an Instance of ImageToDrag

To test the class created above I create a view controller and in the loadView method define a UIView, create an instance of ImageToDrag and add the same as a subview in the current view.

- (void)loadView 
{
  [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
  // Create view that will contain the dragging area
  UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 220, 150)];
  subview.backgroundColor = [UIColor darkGrayColor];
 
  // Create an instance of the image to drag
  ImageToDrag *img = [[ImageToDrag alloc] initWithImage:[UIImage imageNamed:@"iOSDevTips.png"]];
  img.center = CGPointMake(110, 75);
  img.userInteractionEnabled = YES;
  [subview addSubview:img];
  [img release];
 
  [self.view  addSubview:subview];
  [subview release];
}

Two screenshots of the completed application are below:

Source Code

You can download the Xcode project here: Drag an Image within the Bounds of Superview

11 Comments

  1. just copied and pasted this into my project – worked out of the box!

  2. Absolutely great! Have you got the same code for pinching?
    tx!!!

  3. I tweaked touchesMoved:withEvent in order to work with images that are bigger than the parent container:

    – (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
    {
    CGFloat imgWidth = self.bounds.size.width;
    CGFloat imgHeight = self.bounds.size.height;
    CGFloat parentWidth = self.superview.bounds.size.width;
    CGFloat parentHeight = self.superview.bounds.size.height;
    CGFloat extraWidth = imgWidth – parentWidth;
    CGFloat extraHeight = imgHeight – parentHeight;

    // Get active location upon move
    CGPoint activePoint = [[touches anyObject] locationInView:self];

    // Determine new point based on where the touch is now located
    CGPoint newPoint = CGPointMake(self.center.x + (activePoint.x – currentPoint.x),
    self.center.y + (activePoint.y – currentPoint.y));

    //——————————————————–
    // Make sure we stay within the bounds of the parent view
    //——————————————————–
    float midPointX = CGRectGetMidX(self.bounds);
    // If too far right…
    if (newPoint.x > (self.superview.bounds.size.width – midPointX + extraWidth) && currentPoint.x < activePoint.x)
    {
    newPoint.x = self.superview.bounds.size.width – midPointX + extraWidth;
    }
    else if (newPoint.x activePoint.x)) // If too far left…
    {
    newPoint.x = midPointX – extraWidth;
    }

    float midPointY = CGRectGetMidY(self.bounds);
    // If too far down…
    if (newPoint.y > (self.superview.bounds.size.height – midPointY + extraHeight) && currentPoint.y < activePoint.y)
    {
    newPoint.y = self.superview.bounds.size.height – midPointY + extraHeight;
    }
    else if (newPoint.y activePoint.y)) // If too far up…
    {
    newPoint.y = midPointY – extraHeight;
    }

    // Set new center location
    self.center = newPoint;
    }

  4. Thanks a lot !
    It helped me a lot after try too many solutions.

  5. Hello, is currentPoint really needed? You could use previousLocationInView as below:

    – (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
    {
    //NSLog(@”%s”, __PRETTY_FUNCTION__);

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];
    CGPoint previous = [touch previousLocationInView:self];

    if (!CGAffineTransformIsIdentity(self.transform)) {
    location = CGPointApplyAffineTransform(location, self.transform);
    previous = CGPointApplyAffineTransform(previous, self.transform);
    }

    self.frame = CGRectOffset(self.frame,
    (location.x – previous.x),
    (location.y – previous.y));
    }

Comments are closed.