Search code examples
iosswiftuigesturerecognizer

Want to restrict area of pan gesture on one imageView swift 5


I have one imageview which has 2 gestureRecognizers 1)Pinch 2)Pan

I am able to pinch an image and zoom using scale property what I am not able to achieve is that when I zoom that image and drag to all 4 sides I can drag image and I am able to see the background view behind the imageview want to restrict drag of the imageview till that zommed image is shown

Here is the code for pinch and pan gesture

  @objc func pinchRecognized(pinch: UIPinchGestureRecognizer) {

            if let view = pinch.view {
                view.transform = view.transform.scaledBy(x: pinch.scale, y: pinch.scale)
                pinch.scale = 1
            }
        }
    
    @objc func PangestureMethod(gestureRecognizer: UIPanGestureRecognizer){
       guard gestureRecognizer.view != nil else {return}
       let piece = gestureRecognizer.view!
  
    
       let translation = gestureRecognizer.translation(in: piece.superview)
        
       if gestureRecognizer.state == .began {
          self.initialCenter = piece.center
       }
       if gestureRecognizer.state != .cancelled {
          let newCenter = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + translation.y)
          piece.center = newCenter
       }
       else {
          piece.center = initialCenter
       }
    }

Solution

  • First, it's (probably) easier to reset the Pan Gesture's Translation so we're calculating relative movement:

    if gestureRecognizer.state != .cancelled {
        
        // translation will be + or - a small number of points
        // do what's needed to move the view
    
        // reset recognizer
        gestureRecognizer.setTranslation(.zero, in: superV)
    
    }
    

    Otherwise, let's say you drag (pan) right 300-pts, but the view can only move 20-pts, then it won't start moving back to the left until you've dragged 280-pts left.

    So, when dragging horizontally and we want to stop when the drag-view is at the left or right edge of its superView...

    • calculate MAX centerX position
      • that will be 1/2 of the drag-view's width
    • calculate MIN centerX position
      • that will be width-of-superView minus 1/2 of the drag-view's width

    As an example, if the superView's width is 100, and the drag-view's width is 200, the MAX centerX will be 100 and the MIN centerX will be Zero.

    Then we do the same thing for the centerY position.

    Try using this as your Pan Gesture handler:

    @objc func PangestureMethod(gestureRecognizer: UIPanGestureRecognizer){
        
        // unwrap the view from the gesture
        // AND
        // unwrap that view's superView
        guard let piece = gestureRecognizer.view,
              let superV = piece.superview
        else {
            return
        }
        
        let translation = gestureRecognizer.translation(in: superV)
        
        if gestureRecognizer.state == .began {
            self.initialCenter = piece.center
        }
        if gestureRecognizer.state != .cancelled {
    
            // what the new centerX and centerY will be
            var newX: CGFloat = piece.center.x + translation.x
            var newY: CGFloat = piece.center.y + translation.y
    
            // MAX centerX is 1/2 the width of the piece's frame
            let mxX = piece.frame.width * 0.5
            
            // MIN centerX is Width of superView minus 1/2 the width of the piece's frame
            let mnX = superV.bounds.width - piece.frame.width * 0.5
            
            // make sure new centerX is neither greater than MAX nor less than MIN
            newX = max(min(newX, mxX), mnX)
            
            // MAX centerY is 1/2 the height of the piece's frame
            let mxY = piece.frame.height * 0.5
            
            // MIN centerY is Height of superView minus 1/2 the height of the piece's frame
            let mnY = superV.bounds.height - piece.frame.height * 0.5
            
            // make sure new centerY is neither greater than MAX nor less than MIN
            newY = max(min(newY, mxY), mnY)
            
            // set the new center
            piece.center = CGPoint(x: newX, y: newY)
            
            // reset recognizer
            gestureRecognizer.setTranslation(.zero, in: superV)
    
        }
        else {
            piece.center = initialCenter
        }
    }
    

    Edit

    To prevent the Pinch Gesture from scaling down the view to smaller than its superView frame, we can apply the new scale value from the gesture to a CGRect of the view's frame before applying it to the view.

    Then, only apply the scaling to the view if the resulting rect would not be smaller than the superView's frame.

    Give this a try:

    @objc func pinchRecognized(pinch: UIPinchGestureRecognizer) {
    
        // unwrap the view from the gesture
        // AND
        // unwrap that view's superView
        guard let piece = pinch.view,
              let superV = piece.superview
        else {
            return
        }
    
        // this is a bit verbose for clarity
        
        // get the new scale
        let sc = pinch.scale
        
        // get current frame of "piece" view
        let currentPieceRect = piece.frame
        
        // apply scaling transform to the rect
        let futureRect = currentPieceRect.applying(CGAffineTransform(scaleX: sc, y: sc))
        
        // if the resulting rect's width will be
        //  greater-than-or-equal to superView's width
        if futureRect.width >= superV.bounds.width {
            // go ahead and scale the piece view
            piece.transform = piece.transform.scaledBy(x: sc, y: sc)
        }
        pinch.scale = 1
        
    }