Simple Drag and Drop on iPhone

Drag and drop is an intuitive way for users to interact with an application. But it might not be obvious to the developer how to implement such an interface. Today we’ll look at drag and drop: we’ll build a very simple app to change a view’s color depending on the background color of an image view dragged into it. So let’s get started!

Start Xcode, choose “Create a new Xcode project.” Select the Single View Application template and click next. Name the project “DragDrop,” and give it the options shown here:

Click Next, choose a location to save your project, and click Create.

Open the ViewController.zib file, and add four UIImageViews and a UIView to the interface as shown:

Change the background color of each image view; all the image views should have a different color. (Note that you will not be able to see the background color of an image view in the interface builder.)

In the ViewController.h file, we’ll add the following:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (nonatomic, strong) IBOutlet UIView *dropTarget;
@property (nonatomic, strong) UIImageView *dragObject;
@property (nonatomic, assign) CGPoint touchOffset;
@property (nonatomic, assign) CGPoint homePosition;

@end

We’ve added an outlet property to represent the UIView object we added to the interface. The dragObject property will represent the image view being dragged, touchOffset is the distance between the actual touch point and the upper left corner of the dragObject, and homePosition will keep the dragObject’s original position on the screen.

In order to implement drag and drop, we have to track not only when touches occur, but how they move about the screen (drag) and where they end (drop). Every view controller adopts four delegate methods that assist in this process. They are:

?     (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
?     (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
?     (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
?     (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

In this demonstration, we’ll make use of the first three of these methods; touchesCancelled: withEvent: is called only when a system event (like a low memory warning) cancels the touch event. (In a production app, we would use this method to do “clean up” if necessary.)

Open ViewController.m and add the following between the @implementation line and the viewDidLoad method:

@synthesize dropTarget;
@synthesize dragObject;
@synthesize touchOffset;
@synthesize homePosition;

(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if ([touches count] == 1) {
        // one finger
        CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
        for (UIImageView *iView in self.view.subviews) {
            if ([iView isMemberOfClass:[UIImageView class]]) {
                if (touchPoint.x > iView.frame.origin.x &&
                    touchPoint.x < iView.frame.origin.x + iView.frame.size.width &&
                    touchPoint.y > iView.frame.origin.y &&
                    touchPoint.y < iView.frame.origin.y + iView.frame.size.height)
                {
                    self.dragObject = iView;
                    self.touchOffset = CGPointMake(touchPoint.x iView.frame.origin.x,
                                                   touchPoint.y iView.frame.origin.y);
                    self.homePosition = CGPointMake(iView.frame.origin.x,
                                                    iView.frame.origin.y);
                    [self.view bringSubviewToFront:self.dragObject];
                }
            }
        }
    }
}

(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
    CGRect newDragObjectFrame = CGRectMake(touchPoint.x touchOffset.x,
                                           touchPoint.y touchOffset.y,
                                           self.dragObject.frame.size.width,
                                           self.dragObject.frame.size.height);
    self.dragObject.frame = newDragObjectFrame;
}

(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
    if (touchPoint.x > self.dropTarget.frame.origin.x &&
        touchPoint.x < self.dropTarget.frame.origin.x + self.dropTarget.frame.size.width &&
        touchPoint.y > self.dropTarget.frame.origin.y &&
        touchPoint.y < self.dropTarget.frame.origin.y + self.dropTarget.frame.size.height )
    {
        self.dropTarget.backgroundColor = self.dragObject.backgroundColor;
       
    }
    self.dragObject.frame = CGRectMake(self.homePosition.x, self.homePosition.y,
                                       self.dragObject.frame.size.width,
                                       self.dragObject.frame.size.height);
}

As always, our first task is to synthesize the properties. Next is the definition of the touchesBegan: withEvent: method. We first make sure that there is only one finger touching the screen. Next, we look at all subviews of the main view, and for each, we see if the touch occurred inside that view’s frame. We also verify that the object touched is an image view (because we don’t want to be able to drag the dropTarget view in this application).

If the view is an image view, and if the touch occurred inside its frame, we then designate the view as the dragObject, get the offset of the touch from the upper left corner of the view as a CGPoint, and “remember” the view’s home position. Otherwise, we do nothing.

In touchesMoved: withEvent: we get the position of the touch, then set the dragObject’s frame to the new touch position minus the touchOffset coordinates (the width and height don’t change). Changing the frame of a UIView (or anything that inherits from UIView) changes its position on the screen.

Lastly, in touchesEnded: withEvent: we observe whether the last touch point is inside the frame of the dropTarget. If it is, we change the background color of the drop target to the background color of the dragObject. Regardless of the dragObject’s position, we send it back to its homePosition. This ensures that the draggable objects will always be in the same location between touch events.

Run the application and drag the image views to the dropTarget.

Notice that as you drag an image view about the screen, it always stays on top of the other views on the screen. This is because in touchesBegan: withEvent: we have called bringSubviewToFront: on the controller’s view, with the dragObject as the subview.

Experiment with this technique. You might want to change the text in a label by dragging some other label to it, or open a text file in a UITextView control by dragging the filename to it from a label, for example. You might also try limiting the kinds of dragObjects a particular dropTarget will accept. There are many things possible; here we have outlined the general method to get you started.

Have Fun!

Leave a Reply

Your email address will not be published. Required fields are marked *