Moving UIView within Parent UIView (UIPanGestureRecognizer)

13,588

Solution 1

First get the new frame of your UIImageView and check if it is completely inside its superView using CGRectContainsRect() method. If yes, then set UImageView's frame to new frame.

- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {

    CGPoint translation = [recognizer translationInView:self.view];
    CGRect recognizerFrame = recognizer.view.frame;
    recognizerFrame.origin.x += translation.x;
    recognizerFrame.origin.y += translation.y; 

    // Check if UIImageView is completely inside its superView
    if (CGRectContainsRect(self.view.bounds, recognizerFrame)) {
        recognizer.view.frame = recognizerFrame;
    }
    // Else check if UIImageView is vertically and/or horizontally outside of its
    // superView. If yes, then set UImageView's frame accordingly.
    // This is required so that when user pans rapidly then it provides smooth translation.
    else {
        // Check vertically
        if (recognizerFrame.origin.y < self.view.bounds.origin.y) {
            recognizerFrame.origin.y = 0;
        }        
        else if (recognizerFrame.origin.y + recognizerFrame.size.height > self.view.bounds.size.height) {
            recognizerFrame.origin.y = self.view.bounds.size.height - recognizerFrame.size.height;
        }

        // Check horizantally
        if (recognizerFrame.origin.x < self.view.bounds.origin.x) {
            recognizerFrame.origin.x = 0;
        }
        else if (recognizerFrame.origin.x + recognizerFrame.size.width > self.view.bounds.size.width) {
            recognizerFrame.origin.x = self.view.bounds.size.width - recognizerFrame.size.width;
        }
    }

    // Reset translation so that on next pan recognition
    // we get correct translation value
    [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}

Make sure that you pass bounds of superView and frame of UIImageView so that both CGRects are in same coordinate system.

Solution 2

Try with:

- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer 
{
    if (gesture.state==UIGestureRecognizerStateChanged || gesture.state == UIGestureRecognizerStateEnded){
        UIView *superview = recognizer.view.superview;
        CGSize superviewSize = superview.bounds.size;
        CGSize thisSize = recognizer.view.size;
        CGPoint translation = [recognizer translationInView:self.view];
        CGPoint center = CGPointMake(recognizer.view.center.x + translation.x,
                                 recognizer.view.center.y + translation.y);

        CGPoint resetTranslation = CGPointMake(translation.x, translation.y);

        if(center.x - thisSize.width/2 < 0)
            center.x = thisSize.width/2;
        else if (center.x + thisSize.width/2 > superviewSize.width)
            center.x = superviewSize.width-thisSize.width/2;
        else
            resetTranslation.x = 0; //Only reset the horizontal translation if the view *did* translate horizontally

        if(center.y - thisSize.height/2 < 0)
            center.y = thisSize.height/2;
        else if(center.y + thisSize.height/2 > superviewSize.height)
            center.y = superviewSize.height-thisSize.height/2;
        else
            resetTranslation.y = 0; //Only reset the vertical translation if the view *did* translate vertically

        recognizer.view.center = center;
        [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
    }
}

This way it won't ever move outside the parent view bounds and it will just "stick" to the edge if you try to move it out of the bounds!

Solution 3

Swift version of micantox answer

let gesture = UIPanGestureRecognizer(target: self, action: #selector(self.wasDragged(gestureRecognizer:)))
imageView.addGestureRecognizer(gesture)
imageView.isUserInteractionEnabled = true

@objc func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.changed || gestureRecognizer.state == UIGestureRecognizerState.ended {
   let superview = gestureRecognizer.view?.superview
   let superviewSize = superview?.bounds.size
   let thisSize = gestureRecognizer.view?.frame.size
   let translation = gestureRecognizer.translation(in: self.view)
   var center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
   var resetTranslation = CGPoint(x: translation.x, y: translation.y)

   if center.x - (thisSize?.width)!/2 < 0 {
       center.x = (thisSize?.width)!/2
       } else if center.x + (thisSize?.width)!/2 > (superviewSize?.width)! {
       center.x = (superviewSize?.width)!-(thisSize?.width)!/2
       } else {
         resetTranslation.x = 0 //Only reset the horizontal translation if the view *did* translate horizontally
       }

   if center.y - (thisSize?.height)!/2 < 0 {
       center.y = (thisSize?.height)!/2
      } else if center.y + (thisSize?.height)!/2 > (superviewSize?.height)! {
       center.y = (superviewSize?.height)!-(thisSize?.height)!/2
      } else {
        resetTranslation.y = 0 //Only reset the vertical translation if the view *did* translate vertically
      }

      gestureRecognizer.view?.center = center
      gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
    } 
}

Solution 4

You'll have to set the recognizer.view.center to a new value only if its frame is inside the bounds of its parent view. Use CGRectContainsRect on recognizer.view.superview.bounds and recognizer.view.frame to verify that they are contained.

If you want to allow the image view to move outside its parent view until the center point of the view is outside the parent's bounds, you can use the convertPoint:toView method of UIView and verify that the new CGPoint is not outside your parent's bounds.

Share:
13,588

Related videos on Youtube

ORStudios
Author by

ORStudios

Updated on October 27, 2022

Comments

  • ORStudios
    ORStudios about 1 year

    I am using the following code to move a UIImageView that is present inside of a UIView.

    - (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
    
    CGPoint translation = [recognizer translationInView:self.view];
    recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
                                         recognizer.view.center.y + translation.y);
    [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
    

    }

    I would like to make it so that the UIView does not move outside of the parent view. At the minute the image view is able to move across the entire screen.

  • micantox
    micantox over 10 years
    It's all nice and good except that: you don't know that recognizer.view's superview is self.view AND if a user pans really quickly outside of the superview's bounds, you'll get the ugly effect of the recognizer's frame freezing in the middle of the superview's bounds. Lastly, if this user then keeps panning, the view will keep panning without being under the user's finger anymore. All in all, you can do better.
  • Geek
    Geek over 10 years
    @micantox I agree with some of your points and updated my answer. For superView, I know self.view is imageView's superView because he himself has used self.view as superView in his code.
  • Geek
    Geek over 10 years
    @IconicDigital Look at my updated answer to have correct translation effect in all cases.
  • Reid
    Reid almost 10 years
    I'm not sure what I'm missing, but UIPanGestureRecognizer doesn't have a "frame" property from where I'm sitting...so recognizer.frame does not work. I could use recognizer.view.frame, but then setting recognizer.view.frame to recognizerFrame would seemingly not do anything, as I'd just be assigning the same value back to itself. I tried a different method below which works well for me, but still curious on this one. If someone can advise I'd appreciate it, thanks.
  • Geek
    Geek almost 10 years
    @ReidBelton It is correct that you have to use recognizer.view.frame. Reason why it was not moving can be "you were not changing its value at all" Or "You were changing frame of view other than what you want to drag".