Drag and drop in 3 lines

This post introduces the PuttyView class, which is built to act like a moveable and resizable window-like object in iOS.  It contains one primary view, called the contentView, and moves and resizes that view according to where the user drags their finger. (Source code link at the bottom.)

Drag and drop in 3 lines

Probably the coolest part of this code is the simplicity of getting drag-and-drop functionality in iOS.  Here’s the working implementation methods you need:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  touchStart = [[touches anyObject] locationInView:self];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  CGPoint point = [[touches anyObject] locationInView:self];
  self.center = CGPointMake(self.center.x + point.x - touchStart.x, self.center.y + point.y - touchStart.y);
}

Of course, I’m only counting “actual statement lines” in that “3 line” count, and you also need to declare touchStart as a CGPoint instance variable in the interface of your class. But still, the fact that this little work achieves drag-and-drop functionality is pretty cool.

Adding resizing

It’s just a little more work to add resizing. PuttyView classifies a finger drag as a resize if and only if it starts in the lower-right 15×15 block of the view. Here’s the main code for that:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  touchStart = [[touches anyObject] locationInView:self];
  isResizing = (self.bounds.size.width - touchStart.x < kResizeThumbSize &&
                self.bounds.size.height - touchStart.y < kResizeThumbSize);
  if (isResizing) {
    touchStart = CGPointMake(touchStart.x - self.bounds.size.width,
                             touchStart.y - self.bounds.size.height);
  }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  CGPoint touchPoint = [[touches anyObject] locationInView:self];
  if (isResizing) {
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y,
                            touchPoint.x - touchStart.x, touchPoint.y - touchStart.y);
  } else {
    self.center = CGPointMake(self.center.x + touchPoint.x - touchStart.x,
                              self.center.y + touchPoint.y - touchStart.y);
  }
}

Note that this code uses a new instance variable, a BOOL called isResizing.

PuttyView

The PuttyView class (source code in the demo zip file below) adds a little more to this by accepting a single view (the contentView) as a special subview that is resized in the same manner as PuttyView itself.  I chose to focus on a single special subview because I wanted to force users to explicitly react in their own setFrame: method, without having to worry about layout concerns or make any assumptions about how to resize and refit multiple subviews.

Sample xcode project

PuttyDemo.zip

9 Comments

  1. Posted June 19, 2010 at 8:27 pm | Permalink

    Why use this when the new UiGestureRecognizer is available? You can use that to keep your ui standard with the rest of the system.

  2. Posted June 19, 2010 at 10:17 pm | Permalink

    As I was making this post, I realized I could improve PuttyView by inserting it into the event pipeline at the hitTest level, thus keeping other interactive views like a UITextView still interactive while also being draggable (for example). In that way, I see how PuttyView could be improved.

    However, I’m not quite sure what’s bad about this code in the presence of UIGestureRecognizer. As I understand it, gesture recognizers are designed to be passive objects that simply inform a view about certain gestures. It’s true that you could create a gesture recognizer to recognize dragging, but you would still need code somewhere to actually _perform_ the dragging – and it shouldn’t be in your gesture recognizer. If this code is interacting with the view hierarchy, and you want it to be easily usable with any other view, why not just make a holder view like PuttyView? This approach makes sense to me.

  3. Posted June 20, 2010 at 6:56 pm | Permalink

    The advantage of using UIGestureRecognizer is that you can system standard definitions of gestures. The nice thing about doing it this way is if you wanted to add the long press gesture, double tap, or tap and hold you can easily do so. Here is how you would add pinch, rotate, and pan using UIGestureRecognizers right in PuttyView.

    In initWithFrame: of PuttyView add the gesture recognizers to the view:

    - (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
    // Initialization code

    //Pan Gesture
    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    [self addGestureRecognizer:panGestureRecognizer];
    [panGestureRecognizer release];

    //Rotate Gesture
    UIRotationGestureRecognizer *rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotate:)];
    [self addGestureRecognizer:rotationGestureRecognizer];
    [rotationGestureRecognizer release];

    //Pinch Gesture
    UIPinchGestureRecognizer *pinchGestureRognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
    [self addGestureRecognizer:pinchGestureRognizer];
    [pinchGestureRognizer release];

    }
    return self;
    }

    Then just handle each gesture, for pan:

    -(void) handlePan:(UIGestureRecognizer *) gesture {

    UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *) gesture;

    if (panGesture.state == UIGestureRecognizerStateBegan || panGesture.state == UIGestureRecognizerStateChanged) {

    UIView *view = panGesture.view;
    CGPoint translation = [panGesture translationInView:view.superview];
    [view setCenter:CGPointMake(view.center.x + translation.x, view.center.y + translation.y)];
    [panGesture setTranslation:CGPointZero inView:view.superview];

    }

    }

    For pinch:

    -(void) handlePinch:(UIGestureRecognizer *) gesture {

    UIPinchGestureRecognizer *pinchGesture = (UIPinchGestureRecognizer *) gesture;

    if (pinchGesture.state == UIGestureRecognizerStateBegan || pinchGesture.state == UIGestureRecognizerStateChanged) {

    UIView *view = pinchGesture.view;
    view.transform = CGAffineTransformScale(view.transform, pinchGesture.scale, pinchGesture.scale);
    pinchGesture.scale = 1;

    }

    }

    For rotate:

    -(void) handleRotate:(UIGestureRecognizer *) gesture {

    UIRotationGestureRecognizer *rotateGesture = (UIRotationGestureRecognizer *) gesture;

    if (rotateGesture.state == UIGestureRecognizerStateBegan || rotateGesture.state == UIGestureRecognizerStateChanged) {

    UIView *view = rotateGesture.view;
    view.transform = CGAffineTransformRotate(view.transform, rotateGesture.rotation);
    rotateGesture.rotation = 0;

    }

    }

  4. Posted June 22, 2010 at 12:08 am | Permalink

    You have a good point. Thanks for the code samples, too!

    I plan to look into this more and update this post accordingly in a few days. Thanks again for explaining in so much detail!

  5. G
    Posted December 28, 2010 at 9:59 pm | Permalink

    Tyler,
    Thanks a ton for the article. It saved me lot of time as I was looking for one finger resize option.

    Reese,
    Your suggestion works fine if two fingers resize and rotation is required. But for some apps, one finger rotation works better. So Tyler’s code is still valid.

  6. G
    Posted December 28, 2010 at 10:01 pm | Permalink

    btw, could you shed some light on how to implement one finger rotation ? similar to resize i.e. if i hold the bottom right corner and rotate, the view should be rotated around its center.

    thanks in advance.

  7. Arun
    Posted August 18, 2011 at 2:22 am | Permalink

    Hi,

    I am using this code for dragging and moving subview then i am getting error in the setFrame method please responce me how to solve.

    Thanks,

    Arun

  8. Posted August 31, 2011 at 1:12 pm | Permalink

    Hi @Arun, I just fixed some html artifacts in the code that may have given you an error. Try to re-copy the code from this page. Otherwise, I would definitely need more information to help out. Also, I don’t check the comments very often these days, so you might be better off using stackoverflow or other tools to fix it.

  9. Mahesh Paymal
    Posted December 6, 2011 at 5:59 am | Permalink

    Hi,
    Thanks for this code, i was got stuck for this kind of scaling using UIPanGestureRecognizer.
    This helps a lot to me.

    Regards,
    Mahesh Paymal

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*