views:

1556

answers:

3

Hello community!

I am trying to solve a basic problem with drag and drop on iPhone. Here's my setup:

  • I have a UIScrollView which has one large content subview (I'm able to scroll and zoom it)
  • Content subview has several small tiles as subviews that should be dragged around inside it.

My UIScrollView subclass has this method:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *tile = [contentView pointInsideTiles:[self convertPoint:point toView:contentView] withEvent:event];
    if (tile) {
        return tile;
    } else {
        return [super hitTest:point withEvent:event];
    }
}

Content subview has this method:

- (UIView *)pointInsideTiles:(CGPoint)point withEvent:(UIEvent *)event {
    for (TileView *tile in tiles) {
        if ([tile pointInside:[self convertPoint:point toView:tile] withEvent:event])
            return tile;
    }

    return nil;
}

And tile view has this method:

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
    UITouch *touch = [touches anyObject];   
    CGPoint location = [touch locationInView:self.superview];

    self.center = location;
}

This works, but not fully correct: the tile sometimes "falls down" during the drag process. More precisely, it stops receiving touchesMoved: invocations, and scroll view starts scrolling instead. I noticed that this depends on the drag speed: the faster I drag, the quicker the tile "falls".

Any ideas on how to keep the tile glued to the dragging finger?

Thanks in advance!

A: 

Based on the code you've shared, it looks like your touchesMoved: method will only be called for gestures within the tile. At each touch, you move the tile to be centered on that touch, so slow movements will each give an update within the tile -- and the tile will "catch up" with the fingertip -- before the gesture exits the tile. When a gesture is faster, however, the (x,y) touchesMoved events will be farther apart, and you'll lose the gesture when one (x,y) point is far enough away from the last one that it is outside of the tile already.

You can work around this by capturing the movements in a superview large enough to cover the whole draggable area, and controlling the movement of the tile from within that superview.

By the way, is there a reason you're overriding the hitTest: method? It might be easier (and possibly more efficient?) to use the built-in implementation.

Tyler
Without hitTest: this does not work at all: dragging the tile results in scrolling the whole content subview.I was able to solve the problem however: it turned out that there should be also touchesBegan: and touchesEnded: implementations (in my case having empty methods helped) in the tile, otherwise the gesture started propagating to parent views, and they were intercepting it somehow.
Sergey Mikhanov
A: 

Solved: it turned out that there should be also touchesBegan: and touchesEnded: implementations (in my case having empty methods helped) in the tile, otherwise the gesture started propagating to parent views, and they were intercepting the gesture somehow. Dependency on the drag speed was imaginary.

Sergey Mikhanov
+5  A: 

I was struggling with this same problem - I was trying to do a interface with a lot of "cards" (UIView subclasses) on a cork board, and have the cork board area scrollable, but still able to drag-and-drop the cards. I was doing the hitTest() solution above, but one of the Apple engineers asked me why I was doing it that way. The simpler solution they suggested was as follows:

1) In the UIScrollView class, set the value of canCancelContentTouches to NO - this tells the UIScrollView class to allow touches within subviews (or, in this case, in subviews of subviews).

2) In my "card" class, set exclusiveTouch to YES - this tells the subview it owns the touches inside of it.

After this, I was able to drag around the cards and still scroll the subview. It's a lot simpler and cleaner than the hitTest() solution above.

(BTW, for extra credit, if you are using iOS 3.2 or 4.0 or later, use the UIPanGestureRecognizer class to handle the drag and drop logic - the drag and drop motion is a lot smoother than overriding touchesBegan()/touchesMoved()/touchesEnded().)

frauen1
1 was exactly what I was looking for. canCancelContentTouches seems like a rather poor name for such a variable.
Kendall Hopkins