Getsy
Getsy

Reputation: 4905

iOS: Move images between UIImageViews

I have a view controller, where there is a default UIview placed the whole screen. I have added couple of UIImageViews (say around 20) in this view. I want to support swap the images between these UIImageViews. I have already used UITapGestureRecogniser to handle touch happening on these UIImageViews. Can i handle drag and drop of images between these UIImageViews under this UITapGestureRecogniser events itself? (or) Do i need to use UIPanGestureRecognizer or touch events? What is the simplest and easiest way to achieve drag and drop of images between these UIImageViews? Please suggest?

Thank you!

Upvotes: 2

Views: 1005

Answers (3)

Jon
Jon

Reputation: 3238

Rob's answer was excellent. I modified his handlePan method a little to allow hot swapping of images.

hot swapping

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static UIImageView *draggedImage = nil;
    static CGRect draggedImageLastFrame;

    CGPoint location = [gesture locationInView:gesture.view];

    if (gesture.state == UIGestureRecognizerStateBegan) {

        // see if we grabbed an imageview

        draggedImage = [self determineImageForLocation:location];

        // if so, save its old location for future reference and bring it to the front

        if (draggedImage) {
            draggedImageLastFrame = draggedImage.frame;
            [draggedImage.superview bringSubviewToFront:draggedImage];
            draggedImage.alpha = 0.6f;
        }

    } else if (gesture.state == UIGestureRecognizerStateChanged && draggedImage != nil) {

        // if we're dragging it, then update its horizontal location

        CGPoint translation = [gesture translationInView:gesture.view];
        CGRect frame = draggedImage.frame;
        frame.origin.x += translation.x;
        draggedImage.frame = frame;
        [gesture setTranslation:CGPointZero inView:gesture.view];

        UIImageView *draggedOver = [self draggedImageView:draggedImage toLocation:location];
        if (draggedOver != nil) {

            // animate the draggedOver image to the the draggedImage's last location

            [UIView animateWithDuration:0.25
                             animations:^{
                                 CGRect currentFrame = draggedOver.frame;
                                 draggedOver.frame = draggedImageLastFrame;
                                 draggedImageLastFrame = currentFrame;
                             }];
        }

    } else if (draggedImage != nil && (gesture.state == UIGestureRecognizerStateEnded ||
                                       gesture.state == UIGestureRecognizerStateCancelled ||
                                       gesture.state == UIGestureRecognizerStateFailed)) {

        // finished, animate the draggedImage into place

        [UIView animateWithDuration:0.25
                         animations:^{
                             draggedImage.frame = draggedImageLastFrame;
                             draggedImage.alpha = 1.0f;
                         }];
    }
}

Upvotes: 2

Orbitus007
Orbitus007

Reputation: 696

Do not use tap gestures at all in fact.

The secret is to use the superview of the imageView.

You need 3 image view's....

UIImageView *ImageViewA

UIImageView *ImageViewB

UIImageView *DraggableImageView

Suppose you click the screen and the host view gets a touchesBegan event... if you take the ImageViewA and test it's rect with the following method:

[rect containsPoint:touchPoint];

If this is true and you have successfully touched the first imageView... begin the dragging operation in the touchesMoved like follows:

If you take ImageViewA.superview you will get the view's host view... The host view should then recieve the following commands:

[newDraggableImageView setFrame:ImageViewA.frame];
[newDraggableImageView setImage:ImageViewA.image];
[hostView addSubview:newDraggableImageView];

now you track the touch events on the host view with the UIResponder methods touchesBegain touchesMoved and touchesEnded

as you go through touchesMoved: send the coordinate of the movement into the frame of the newDraggableImageView...

as it is dragging... one the touchesEnded method:

if the frame of the image is inside the frame of the second drag spot then you can set the final image view

if ([frame containsRect:anotherFrame])
    ImageViewB.image = draggableImageView.image;

Sorry this is in pseudo code... hope you uderstand the logical flow...add the image to the host view... or the application's window view and move it around... once movement is ended..test for the position of the imageView and set the new image view appropriately...

GOODLUCK! and Happy Coding!

Upvotes: 0

Rob
Rob

Reputation: 437432

A couple of thoughts.

  1. You can achieve this with tap gestures (e.g. tap first image view, tap on destination image view), but it's not very intuitive. Doing a proper drag-and-drop with a UIPanGestureRecognizer will be far more intuitive for your end user.

  2. Technically, you're not dragging an image from the image view, but rather you'll actually be dragging the image view itself. (An image, itself, has no visual representation without an image view.) And when you let go, you'll animate the changing of the image view frames to complete the illusion.


If you had a NSArray of imageViews, you could add a gesture to their superview:

@property (nonatomic, strong) NSMutableArray *imageViews;

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self createImageViewArray];

    UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                                              action:@selector(handlePan:)];
    [self.view addGestureRecognizer:gesture];
}

- (void)createImageViewArray
{
    self.imageViews = [NSMutableArray array];

    // add your imageviews to the array any way you want

    ...
}

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static UIImageView *draggedImage = nil;
    static CGRect draggedImageOriginalFrame;

    CGPoint location = [gesture locationInView:gesture.view];

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        // see if we grabbed an imageview

        draggedImage = [self determineImageForLocation:location];

        // if so, save its old location for future reference and bring it to the front

        if (draggedImage)
        {
            draggedImageOriginalFrame = draggedImage.frame;
            [draggedImage.superview bringSubviewToFront:draggedImage];
        }
    }
    else if (gesture.state == UIGestureRecognizerStateChanged && draggedImage != nil)
    {
        // if we're dragging it, then update its location 

        CGPoint translation = [gesture translationInView:gesture.view];
        CGRect frame = draggedImageOriginalFrame;
        frame.origin.x += translation.x;
        frame.origin.y += translation.y;
        draggedImage.frame = frame;
    }
    else if (draggedImage != nil && (gesture.state == UIGestureRecognizerStateEnded ||
                                     gesture.state == UIGestureRecognizerStateCancelled ||
                                     gesture.state == UIGestureRecognizerStateFailed))
    {
        // if we let go, let's see if we dropped it over another image view

        UIImageView *droppedOver = nil;

        if (gesture.state == UIGestureRecognizerStateEnded)
            droppedOver = [self draggedImageView:draggedImage toLocation:location];

        if (droppedOver == nil)
        {
            // fail; animate the restoring of the view back to it's original position

            [UIView animateWithDuration:0.25
                             animations:^{
                                 draggedImage.frame = draggedImageOriginalFrame;
                             }];
        }
        else
        {
            // succeed; make sure to bring the view we're about to animate to the front

            [droppedOver.superview bringSubviewToFront:droppedOver];

            // animate the swapping of the views

            [UIView animateWithDuration:0.25
                             animations:^{
                                 draggedImage.frame = droppedOver.frame;
                                 droppedOver.frame = draggedImageOriginalFrame;
                             }];
        }
    }
}

// see if we dragged an imageview over one of our imageviews, returning a point
// to the image view we dropped it over

- (UIImageView *)draggedImageView:(UIImageView *)draggedView toLocation:(CGPoint)location
{
    for (UIImageView *imageview in self.imageViews)
        if (CGRectContainsPoint(imageview.frame, location) && imageview != draggedView)
            return imageview;

    return nil;
}

// see if we started the gesture over an imageview
// (turns out that this is the same as "did we drag an image over another",
// but as we're not dragging an image yet, just pass nil)

- (UIImageView *)determineImageForLocation:(CGPoint)location
{
    return [self draggedImageView:nil toLocation:location];
}

Upvotes: 3

Related Questions