问题
I've implemented the UIPinchGestureRecognizer on a UIImageView in my app, however no matter where I pinch on the image, it seems to zoom into the same spot. Does anyone know how I can make it zoom in to where a user actually "pinches"? See code below.
ViewController.m
- (IBAction)scaleImage:(UIPinchGestureRecognizer *)recognizer {
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
recognizer.scale = 1;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
{
BOOL shouldReceiveTouch = YES;
if (gestureRecognizer == tap) {
shouldReceiveTouch = (touch.view == featureImage);
}
return shouldReceiveTouch;
}
回答1:
A scale transform leaves the origin (0, 0) untouched. So to scale a view around a particular point, you must first translate that point to the origin, then apply the scale, then translate back.
- (IBAction)pinchGestureDidFire:(UIPinchGestureRecognizer *)pinch {
First, we get the view being pinched.
UIView *pinchView = pinch.view;
To compute the center of the pinch, we'll need the midpoint of the view's bounds, so we get the bounds too:
CGRect bounds = pinchView.bounds;
The center is based on the centroid of the pinch's touches, which we get this way:
CGPoint pinchCenter = [pinch locationInView:pinchView];
But we actually need the pinch offset relative to the center of the view, because the view's transform is relative to the center of the view by default. (You can change this by changing the view's layer.anchorPoint
.)
pinchCenter.x -= CGRectGetMidX(bounds);
pinchCenter.y -= CGRectGetMidY(bounds);
Now we can update the view's transform. First we get its current transform:
CGAffineTransform transform = pinchView.transform;
Then we update it to translate the pinch center to the origin:
transform = CGAffineTransformTranslate(transform, pinchCenter.x, pinchCenter.y);
Now we can apply the scale:
CGFloat scale = pinch.scale;
transform = CGAffineTransformScale(transform, scale, scale);
Then we translate the view back:
transform = CGAffineTransformTranslate(transform, -pinchCenter.x, -pinchCenter.y);
Now we can update the view with the modified transform:
pinchView.transform = transform;
Finally, we reset the gesture recognizer's scale, since we've applied the current scale:
pinch.scale = 1.0;
}
Demo:
Note that in the simulator, you can hold option (alt) for a pinch gesture. Holding shift (while holding option) moves the two touches together.
Here's the code all together for copy/paste:
- (IBAction)pinchGestureDidFire:(UIPinchGestureRecognizer *)pinch {
UIView *pinchView = pinch.view;
CGRect bounds = pinchView.bounds;
CGPoint pinchCenter = [pinch locationInView:pinchView];
pinchCenter.x -= CGRectGetMidX(bounds);
pinchCenter.y -= CGRectGetMidY(bounds);
CGAffineTransform transform = pinchView.transform;
transform = CGAffineTransformTranslate(transform, pinchCenter.x, pinchCenter.y);
CGFloat scale = pinch.scale;
transform = CGAffineTransformScale(transform, scale, scale);
transform = CGAffineTransformTranslate(transform, -pinchCenter.x, -pinchCenter.y);
pinchView.transform = transform;
pinch.scale = 1.0;
}
回答2:
Swift implementation of rob's answer:
@objc private func pinchHandler(gesture: UIPinchGestureRecognizer) {
if let view = gesture.view {
switch gesture.state {
case .changed:
let pinchCenter = CGPoint(x: gesture.location(in: view).x - view.bounds.midX,
y: gesture.location(in: view).y - view.bounds.midY)
let transform = view.transform.translatedBy(x: pinchCenter.x, y: pinchCenter.y)
.scaledBy(x: gesture.scale, y: gesture.scale)
.translatedBy(x: -pinchCenter.x, y: -pinchCenter.y)
view.transform = transform
gesture.scale = 1
case .ended:
// Nice animation to scale down when releasing the pinch.
// OPTIONAL
UIView.animate(withDuration: 0.2, animations: {
view.transform = CGAffineTransform.identity
})
default:
return
}
}
}
回答3:
Rob Mayoff's (@robmayoff) answer is truly inspiring and I wish I had found it earlier. But I actually solved this by changing the anchor point rather than using a transform translation. Effectively it has the same result.
I have not seen this solution on SO (yet...) and figured this was a good place for it.
Transforms work relative to the view layer anchor point. But you can't just set the position point to the pinch center point if the view has been transformed. So here's a method to set the layer anchor point AND correctly convert the position point as well:
// change the anchor point for the view layer
- (void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view {
// sanity check - x and y MUST be between 0 and 1
if (anchorPoint.x < 0 || anchorPoint.x > 1 ||
anchorPoint.y < 0 || anchorPoint.y > 1) {
return;
}
CGPoint newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x,
view.bounds.size.height * anchorPoint.y);
CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x,
view.bounds.size.height * view.layer.anchorPoint.y);
newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);
CGPoint position = view.layer.position;
position.x -= oldPoint.x;
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
view.layer.position = position;
view.layer.anchorPoint = anchorPoint;
}
And here's the handlePinch
method that uses the setAnchorPoint
method to set the layer anchor point to the center point of the pinch touch points:
- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer {
// make these static so they can be used across gesture states
static CGAffineTransform initialTransform;
static CGPoint initialAnchor;
if (recognizer.state == UIGestureRecognizerStateBegan) {
// save these for later states
initialTransform = recognizer.view.transform;
initialAnchor = recognizer.view.layer.anchorPoint;
// get the center point of the pinch
CGPoint touch = [recognizer locationInView:recognizer.view];
// anchor point is relative to the view bounds: 0 ... up to 1.0, for both x and y
CGFloat anchorX = touch.x / recognizer.view.bounds.size.width;
CGFloat anchorY = touch.y / recognizer.view.bounds.size.height;
// set the layer anchor point AND position, to where the view was initially pinched
[self setAnchorPoint:CGPointMake(anchorX,anchorY) forView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
// perform the pinch zoom
recognizer.view.transform = CGAffineTransformScale(initialTransform,recognizer.scale,recognizer.scale);
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
// reset the scale when it's done
recognizer.scale = 1;
// restore the original anchor point
[self setAnchorPoint:initialAnchor forView:recognizer.view];
}
}
回答4:
You could simply add a scrollView and in the scrollView add a imageView as below,
![ImageView on top of scrollView][1]
Some issue with image upload so check image at this link
Then, you could create a IBOutlet property of scrollView and imageView ,and connect it respectively. Later, add these lines in your viewDidAppear method,
_scrollView.maximumZoomScale = 10.0; //Maximum Zoom
_scrollView.minimumZoomScale = minimumScale; //Minimum Zoom
Also add below method,
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return _imageView;
}
So now you would get the zoom in/out effects and maximum/minimum zoom limit that u set. Lesser code along with achieving ur goal.
回答5:
ViewController.h
@property (strong, nonatomic) IBOutlet UIImageView *img;
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchDetected:)];
[_img addGestureRecognizer:pinchRecognizer];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)pinchDetected:(UIPinchGestureRecognizer *)pinchRecognizer
{
CGFloat scale = pinchRecognizer.scale;
_img.transform = CGAffineTransformScale(self.img.transform, scale, scale);
pinchRecognizer.scale = 1.0;
}
来源:https://stackoverflow.com/questions/25859069/using-pinchgesture-how-can-i-zoom-in-to-where-a-users-fingers-actually-pinch