Hashem Aboonajmi
Hashem Aboonajmi

Reputation: 13890

Restrict a draggable UIView to bounds of SuperView

assume we have a UITableViewCell with an inner big custom UIImageView, like the image below: I have added UILongPressGestureRecOgnizer to the UITableViewCell, so after longPress user can move this big image. but I want this movement be in the bound of UITableViewCell. enter image description here

What I've done right now:

UIView *view = sender.view;
CGPoint point = [sender locationInView:view.superview];
if (sender.state == UIGestureRecognizerStateChanged)
{
    CGPoint oldCenter = view.center;
    CGPoint newCenter = oldCenter;
    CGFloat deltaX = point.x - _priorPoint.x;
    CGFloat deltaY = point.y - _priorPoint.y;
    newCenter.x += deltaX;
    newCenter.y += deltaY;
    CGRect oldFrame = view.frame;
    CGRect newFrame = CGRectMake(oldFrame.origin.x + deltaX, oldFrame.origin.y+deltaY, view.width, view.height);

    if (CGRectContainsRect(newFrame, self.bounds)) {
        self.customImageView.center = newCenter;
    }
    else
    {
        CGFloat newX = newFrame.origin.x;
        CGFloat newY = newFrame.origin.y;
        if (newX<view.x) {
            newX = oldFrame.origin.x;
        }
        else if ((newFrame.origin.x + newFrame.size.width)>view.right)
        {
            newX = oldFrame.origin.x;
        }

        if (newY<view.y) {
            newY = oldFrame.origin.y;
        }
        else if ((newY + newFrame.size.height)>view.bottom)
        {
            newY = view.y;
        }
        NSLog(@"newX:%f, newY:%f", newX, newY);
        newFrame = CGRectMake(newX, newY, view.width, view.height);
        self.customImageView.frame = newFrame;
    }
_priorPoint = point;

the problem is when I move my finger diagonally, the image movement is a little jerky. while moving diagonally, for example when X is out of bound, if Y is valid, Y point should be updated. what's wrong here? do you have any idea?

Upvotes: 1

Views: 581

Answers (1)

Putz1103
Putz1103

Reputation: 6211

What I do for this is a little hard to follow, but here goes...

I do the touch interaction inside the view that is moving. In touchesBegan

drawingFrame = self.frame;
initialViewOrigin = self.frame.origin;
startingTouch = [[touches anyObject] locationInView:[self superview]];

These are all class variables. Then in touchesMoved:

lastTouchPoint = [[touches anyObject] locationInView:[self superview]];
float newLocationX = initialViewOrigin.x + (p.x - startingTouch.x);
float newLocationY = initialViewOrigin.y + (p.y - startingTouch.y);

//Do some location checks for sending it too far off the screen or whatnot

drawingFrame.origin = [self scrollLimit:newLocationX y:newLocationY];

CGRect frame = self.frame;
frame.origin.x = drawingFrame.origin.x;
frame.origin.y = drawingFrame.origin.y;
self.frame = frame;

All the fun stuff happens in the scrollLimit function:

-(CGPoint)scrollLimit:(float)x1 y:(float)y1
{
    //My app always requires landscape, so make sure the parent's sizes are taken accordingly
    int parentHeight = MIN(self.superview.frame.size.width,self.superview.frame.size.height);
    int parentWidth = MAX(self.superview.frame.size.width,self.superview.frame.size.height);

    if(self.frame.size.width < parentWidth)
    {
        if(x1 < 1)
        {
            x1 = 1;
        }
        else if(x1 >= parentWidth - self.frame.size.width)
        {
            x1 = parentWidth - self.frame.size.width;
        }
    }
    else{
        x1 = initialViewOrigin.x;
    }


    if(self.frame.size.height < parentHeight)
    {
        if(y1 < 1)
        {
            y1 = 1;
        }
        else if(y1 > parentHeight - self.frame.size.height)
        {
            y1 = parentHeight - self.frame.size.height;
        }
    }
    else{
        y1 = initialViewOrigin.y;
    }

    return CGPointMake(x1,y1);

}

All of this makes sure that the view stays inside the bounds that you set (in my case just the superview bounds) and it also makes the dragging experience smooth and realistic. Some other methods I have seen can cause your finger to get off of the item you are scrolling and never sync back up. I'm sure my code won't work for you as is, but it should get you in the right direction.

In my methods I also have simulated inertia for view "throwing" while still keeping the bounding superview box. Since that is outside of your question and it took a very long time to get perfect, I'll leave that as an exercise to the reader...

Upvotes: 2

Related Questions