iosswiftcore-imagecatransform3dcatransformlayer

Limiting UIPinchGestureRecognizer Zoom Levels Objective C to Swift 3.0


iOS 10.2 Swift 3.0

Trying to translate this piece of code from Paul Solt blog. Fixed by SO poster, updated code!

http://paulsolt.com/blog/2011/03/limiting-uipinchgesturerecognizer-zoom-levels

Sample Code

- (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer {

if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = [gestureRecognizer scale];
}

if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {

CGFloat currentScale = [[[gestureRecognizer view].layer     valueForKeyPath:@"transform.scale"] floatValue];

// Constants to adjust the max/min values of zoom
const CGFloat kMaxScale = 2.0;
const CGFloat kMinScale = 1.0;

CGFloat newScale = 1 -  (lastScale - [gestureRecognizer scale]); // new     scale is in the range (0-1)
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
[gestureRecognizer view].transform = transform;

lastScale = [gestureRecognizer scale];  // Store the previous scale factor     for the next pinch gesture call
}
}

Almost done, but can not seem to find a reference to the CATransform key used here for Swift 3.0. My code ...

if sender.state == .began {
        // Reset the last scale, necessary if there are multiple objects with different scales
        lastScale = sender.scale
    }

    if sender.state == .began || sender.state == .changed {

        // UPDATED
        currentScaleX = self.image2P.transform.scaleX
        currentScaleY = self.image2P.transform.scaleY


        self.image2P.transform = self.image2P.transform.scaledBy(x: 1.1, y: 1.1)


        // Constants to adjust the max/min values of zoom
        let kMaxScale:CGFloat = 2.0;
        let kMinScale:CGFloat = 1.0;

        var newScale = 1 -  (lastScale - sender.scale) // new scale is in the range (0-1)
        newScale = min(newScale, kMaxScale / currentScaleX)
        newScale = max(newScale, kMinScale / currentScaleY)
        self.image2P.transform = self.image2P.transform.scaledBy(x: newScale, y: newScale)

        lastScale = sender.scale  // Store the previous scale factor for the next pinch gesture call
    }

Solution

  • You should store xScale and yScale separately because generally speaking there is no guarantee they are equal.

    extension CGAffineTransform {
        var scaleX: CGFloat {
            return (a > 0 ? 1 : -1) * sqrt (a*a + c*c)
        }
    
        var scaleY: CGFloat {
            return (d > 0 ? 1 : -1) * sqrt (b*b + d*d)
        }
    }
    

    These extension methods will work return correct scale factors even if your view is also rotated and/or translated:

    view.transform = CGAffineTransform(scaleX: 1.5, y: 1.2).rotated(by: .pi/3.0).translatedBy(x: 50, y: 30)
    print("scaleX = \(view.transform.scaleX), scaleY = \(view.transform.scaleY)")
    

    Output:

    scaleX = 1.5, scaleY = 1.2

    It may be not obvious, but a, b, c and d properties are elements of the transform matrix. You can find more details in Quartz 2D Programming Guide. Also you can find math details here. Just be aware that b and c element names are swapped in these two sources.