Basic Drag and Drop in iOS

74,413

Solution 1

Assume you have a UIView scene with a background image and many vehicles, you may define each new vehicle as a UIButton (UIImageView will probably work too):

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(imageTouch:withEvent:) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button setImage:[UIImage imageNamed:@"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];

Then you may move the vehicle wherever you want, by responding to the UIControlEventTouchDragInside event, e.g.:

- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
    CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
    UIControl *control = sender;
    control.center = point;
}

It's a lot easier for individual vehicle to handle its own drags, comparing to manage the scene as a whole.

Solution 2

In addition to ohho's answer I've tried similar implementation, but without problem of "fast" drag and centering to drag.

The button initialization is...

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragOutside];
[button setImage:[UIImage imageNamed:@"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];

and method implementation:

- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
    UIControl *control = sender;

    UITouch *t = [[event allTouches] anyObject];
    CGPoint pPrev = [t previousLocationInView:control];
    CGPoint p = [t locationInView:control];

    CGPoint center = control.center;
    center.x += p.x - pPrev.x;
    center.y += p.y - pPrev.y;
    control.center = center;
}

I don't say, that this is the perfect solution for the answer. Nevertheless I found this the easiest solution for dragging.

Solution 3

I had a case where I would like to drag/drop uiviews between multiple other uiviews. In that case I found the best solution to trace the pan events in a super view containing all the drop zones and all the drag gable objects. The primary reason for NOT adding the event listeners to the actual dragged views was that I lost the ongoing pan events as soon as I changed superview for the dragged view.

Instead I implemented a rather generic drag drop solution that could be used on single or multiple drop zones.

I have created a simple example that can be seen here: Drag an drop uiviews between multiple other uiviews

If you decide to go the other way, try using uicontrol instead of uibutton. They declare the addTarget methods and are much easier to extend - hence give custom state and behavior.

Solution 4

-(void)funcAddGesture
{ 

 // DRAG BUTTON
        UIPanGestureRecognizer *panGestureRecognizer;
        panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragButton:)];
        panGestureRecognizer.cancelsTouchesInView = YES;

        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(50, 100, 60, 60);
        [button addTarget:self action:@selector(buttontapEvent) forControlEvents:UIControlEventPrimaryActionTriggered];
        [button setImage:[UIImage imageNamed:@"button_normal.png"] forState:UIControlStateNormal];
        [button addGestureRecognizer:panGestureRecognizer];
        [self.view addSubview:button];
}


- (void)dragButton:(UIPanGestureRecognizer *)recognizer {

    UIButton *button = (UIButton *)recognizer.view;
    CGPoint translation = [recognizer translationInView:button];

    float xPosition = button.center.x;
    float yPosition = button.center.y;
    float buttonCenter = button.frame.size.height/2;

    if (xPosition < buttonCenter)
        xPosition = buttonCenter;
    else if (xPosition > self.view.frame.size.width - buttonCenter)
        xPosition = self.view.frame.size.width - buttonCenter;

    if (yPosition < buttonCenter)
        yPosition = buttonCenter;
    else if (yPosition > self.view.frame.size.height - buttonCenter)
        yPosition = self.view.frame.size.height - buttonCenter;

    button.center = CGPointMake(xPosition + translation.x, yPosition + translation.y);
    [recognizer setTranslation:CGPointZero inView:button];
}


- (void)buttontapEvent {

    NSLog(@"buttontapEvent");
}

Solution 5

I would actually track dragging on the vehicle view itself, rather than the large view - unless there is a particular reason no to.

In one case where I allow the user to place items by dragging them on the screen. In that case I experimented with both having the top view and the child views draggable. I found it's cleaner code if you add a few "draggable" views to the UIView and handle how to they could be dragged. I used a simple callback to the parent UIView to check if the new location was suitable or not - so I could indicate with animations.

Having the top view track dragging I guess is as good, but that makes it slightly more messy if you would like to add non-draggable views that still interact with the user, such as a button.

Share:
74,413
Luke
Author by

Luke

Check out Glossology, my latest collection of constrained poetry.

Updated on June 21, 2020

Comments

  • Luke
    Luke almost 4 years

    I want to have a view in which there are vehicles driving around that the user can also drag and drop. What do you think is the best large-scale strategy for doing this? Is it best to get touch events from the views representing the vehicles, or from the larger view? Is there a simple paradigm you've used for drag and drop that you're satisfied with? What are the drawbacks of different strategies?

  • Luke
    Luke over 13 years
    Looks great and very simple! I'll try this out.
  • Tim
    Tim almost 13 years
    Would this strategy cause dragging to break if the user dragged the button faster than the animation updated? If their finger got outside the box, it would stop receiving the imageMoved:withEvent: calls, correct?
  • ymutlu
    ymutlu over 12 years
    How could you know if image stop dragging? How would you know finger moved out?
  • LJ Wilson
    LJ Wilson about 12 years
    ohho - is it possible to offset the dragging "handle" for an image or button so that it can be dragged by the upper right or upper left corner?
  • iosDev
    iosDev about 12 years
    when i am trying getting exception--[drag_move_textViewController imageTouch:withEvent:]: unrecognized selector sent to instance 0x4b4b1c0
  • JakubKnejzlik
    JakubKnejzlik almost 12 years
    @Luke: should this be "fixed" by targeting UIControlEventTouchDragOutside event?
  • Peter Johnson
    Peter Johnson over 11 years
    In your example code, you define UIControl *control=sender after it is used- should that not be defined earlier?
  • JakubKnejzlik
    JakubKnejzlik over 11 years
    @PeterJohnson: In fact it doesn't matter in this case. There is no earlier usage of this variable since the line You've mentioned :)
  • Peter Johnson
    Peter Johnson over 11 years
    What about CGPoint pPrev = [t previousLocationInView:control]; CGPoint p = [t locationInView:control]; These two earlier lines both appear to refer to "control"?
  • JakubKnejzlik
    JakubKnejzlik over 11 years
    Oh gosh! How can I missed that? :-O ... I've edited the answer.
  • d2burke
    d2burke over 11 years
    really...you'd have to be dragging pretty fast :) I'd just recommend not using this for any sort of game, but for typical application, this works.
  • tipycalFlow
    tipycalFlow over 10 years
    @Tim A bit too late but you can try this with UIPanGestureRecognizer too. That wouldn't break for any drag speed.
  • Amr Angry
    Amr Angry about 7 years
    i add the button form interface builder and i use autolayout for some login i do hide the button and show it again when i do so it return back to original place how to prevent this beviour