Reputation: 4905
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
Reputation: 3238
Rob's answer was excellent. I modified his handlePan
method a little to allow hot swapping of images.
- (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
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
Reputation: 437432
A couple of thoughts.
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.
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