Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to drag and move something and scroll at the same time in UIScrollView

I'm writing a app which has a bookshelf view like iBooks.

The problem now is: I can drag and drop a book from on place to another.But how do I make these happen at the same time when I drag book to the bottom of the scrollview:

  1. make the scrollview scroll down
  2. keep the book follow my finger
  3. place the other books at the right place with animation like moving apps in springboard

I know there's an AQGridView in Github, but it seems that the springboard demo doesn't support scroll and move at the same time.(I already set scrollEnable to YES)

like image 777
ultragtx Avatar asked Jul 24 '11 08:07

ultragtx


1 Answers

I ll give you my solution, but since the entire thing is quite big i'll just give you the relevant snippets.

Also, mind you I use a gesture recognizer for the dragging (UILongPressGestureRecognizer) as that is how the user initiates dragging in my app, by keeping his finger pressed on the object. So each subview you can drag around has its own UILongPressGestureRecognizer assigned to it, and the target/selector of that recognizer is in another class which manages both the scrollview and the subviews.

here is the target of the gesture recognizer:

-(void)dragged:(UILongPressGestureRecognizer *)panRecog
{
    if (panRecog.state == UIGestureRecognizerStateBegan)
    {
        UIView * pannedView = panRecog.view;

        dragView = pannedView;
        dragView.center = [panRecog locationInView:scrollView];

        [scrollView bringSubviewToFront:dragView];

        [self startDrag]; // Not important, changes some stuff on screen to show the user he is dragging 
        return;
    }

    if (panRecog.state == UIGestureRecognizerStateChanged)
    {
        int xDelta  = dragView.center.x - [panRecog locationInView:scrollView].x;
        dragView.center = [panRecog locationInView:scrollView];

        [self scrollIfNeeded:[panRecog locationInView:scrollView.superview] withDelta:xDelta];

        return;
    }

    if (panRecog.state == UIGestureRecognizerStateEnded)
    {
        [self endDrag]; // Not important, changes some stuff on screen to show the user he is not dragging anymore
    }
}

The things that are relevant for you:

  • When starting the drag I store which view I drag in dragView and set its center to be the position your finger is relative to the scrollview it is in.
  • startDrag is not important for you, it changes some stuff on screen to show the user he is dragging
  • When we actually move our object ( UIGestureRecognizerStateChanged ) I get the amount of points the user moved his finger and use that delta together with the user's finger position to check if the scrollview needs to be moved in scrollIfNeeded.

This is the code

-(void)scrollIfNeeded:(CGPoint)locationInScrollSuperview withDelta:(int)xDelta
{
    UIView * scrollSuperview = scrollView.superview;
    CGRect bounds = scrollSuperview.bounds;
    CGPoint scrollOffset = scrollView.contentOffset;
    int xOfs = 0;
    int speed = 10;

    if ((locationInScrollSuperview.x > bounds.size.width * 0.7) && (xDelta < 0))
    {
        xOfs = speed * locationInScrollSuperview.x/bounds.size.width;
    }

    if ((locationInScrollSuperview.x < bounds.size.width * 0.3) && (xDelta > 0))
    {
        xOfs = -speed * (1.0f - locationInScrollSuperview.x/bounds.size.width);
    }

    if (xOfs < 0)
    {
        if (scrollOffset.x == 0)    return;
        if (xOfs < -scrollOffset.x) xOfs = -scrollOffset.x;
    }
    scrollOffset.x += xOfs;
    CGRect rect = CGRectMake(scrollOffset.x, 0, scrollView.bounds.size.width, scrollView.bounds.size.height);
    [scrollView scrollRectToVisible:rect animated:NO];
    CGPoint center = dragView.center;
    center.x += xOfs;
    dragView.center=center;
}

This thing only does horizontal scrolling, but handling vertical would be quite similar. What it does is:

  • Check if you dragged into the boundary region of the scrollview ( I use a 30% border left and right as the zone where scrolling should happen). But only if the user moved in that direction ( xDelta < 0 for left, xDelta > 0 for right)
  • next I calculate how much to move the scrollview, using a speed constant and scaling this with how far the user is from the actual border of the scrollview so the speed is proportional to how far to the edge the user is.
  • The last step is to scroll the scrollview with a non-animated scrollRectToVisible. Of course your dragged view moves along so we compensate for that with an opposite offset of its center.

Hopefully this can help you.

like image 90
Joris Mans Avatar answered Oct 10 '22 01:10

Joris Mans