我如何能够将UIPinchGestureRecognizer的比例限制在最小和最大水平?下面的scale属性似乎是相对于上一个已知的比例(来自上一个状态的delta),我无法弄清楚如何设置对被缩放对象的大小/高度的限制。
-(void)scale:(id)sender {
[self.view bringSubviewToFront:[(UIPinchGestureRecognizer*)sender view]];
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {
lastScale = 1.0;
return;
}
CGFloat pinchscale = [(UIPinchGestureRecognizer*)sender scale];
CGFloat scale = 1.0 - (lastScale - pinchscale);
CGAffineTransform currentTransform = [(UIPinchGestureRecognizer*)sender view].transform;
CGAffineTransform holderTransform = holderView.transform;
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
[[(UIPinchGestureRecognizer*)sender view] setTransform:newTransform];
lastScale = [(UIPinchGestureRecognizer*)sender scale];
}
答案 0 :(得分:106)
这是我在使用Anomie的答案作为起点后想出的解决方案。
- (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]);
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
}
}
答案 1 :(得分:18)
无法限制UIPinchGestureRecognizer
的比例。要限制代码中的高度,您应该可以执行以下操作:
CGFloat scale = 1.0 - (lastScale - pinchscale);
CGRect bounds = [(UIPinchGestureRecognizer*)sender view].bounds;
scale = MIN(scale, maximumHeight / CGRectGetHeight(bounds));
scale = MAX(scale, minimumHeight / CGRectGetHeight(bounds));
要限制宽度,请在最后两行中将“高度”更改为“宽度”。
答案 2 :(得分:4)
我从保罗·索尔特和Anoime的答案中收集了一些信息,并将其添加到我为UIViewController制作的现有类别中,以允许任何UIView可拖动,现在使用手势和变换使其变得可追踪。
注意:这会弄脏您正在进行可拖动/可追踪的视图的标记属性。因此,如果您需要其他标记,可以考虑将该值放在此技术使用的NSMutableDictionary中。这可以作为 [self dictForView:theView]
在您的项目中实施:
您可以在视图控制器“视图”中使任何子视图可拖动或可追踪(或两者) 在viewDidLoad中放置一行代码(例如:)
[self makeView:mySubView draggable:YES pinchable:YES minPinchScale:0.75 maxPinchScale:1.0];
在viewDidUnload(发布来宾和字典)中将其关闭:
[self makeView:mySubView draggable:NO pinchable:NO minPinchScale:1.0 maxPinchScale:1.0];
DragAndPinchScale.h文件
#import <UIKit/UIKit.h>
@interface UIViewController (DragAndPinchScale)
-(void) makeView:(UIView*)aView
draggable:(BOOL)draggable
pinchable:(BOOL)pinchable
minPinchScale:(CGFloat)minPinchScale
maxPinchScale:(CGFloat)maxPinchScale;
-(NSMutableDictionary *) dictForView:(UIView *)theView;
-(NSMutableDictionary *) dictForViewGuestures:(UIGestureRecognizer *)guesture;
@end
DragAndPinchScale.m文件
#import "DragAndPinchScale.h"
@implementation UIViewController (DragAndPinchScale)
-(NSMutableDictionary *) dictForView:(UIView *)theView{
NSMutableDictionary *dict = (NSMutableDictionary*) (void*) theView.tag;
if (!dict) {
dict = [[NSMutableDictionary dictionary ] retain];
theView.tag = (NSInteger) (void *) dict;
}
return dict;
}
-(NSMutableDictionary *) dictForViewGuestures:(UIGestureRecognizer *)guesture {
return [self dictForView:guesture.view];
}
- (IBAction)fingersDidPinchInPinchableView:(UIPinchGestureRecognizer *)fingers {
NSMutableDictionary *dict = [self dictForViewGuestures:fingers];
UIView *viewToZoom = fingers.view;
CGFloat lastScale;
if([fingers state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = [fingers scale];
} else {
lastScale = [[dict objectForKey:@"lastScale"] floatValue];
}
if ([fingers state] == UIGestureRecognizerStateBegan ||
[fingers state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[fingers view].layer valueForKeyPath:@"transform.scale"] floatValue];
// limits to adjust the max/min values of zoom
CGFloat maxScale = [[dict objectForKey:@"maxScale"] floatValue];
CGFloat minScale = [[dict objectForKey:@"minScale"] floatValue];
CGFloat newScale = 1 - (lastScale - [fingers scale]);
newScale = MIN(newScale, maxScale / currentScale);
newScale = MAX(newScale, minScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[fingers view] transform], newScale, newScale);
viewToZoom.transform = transform;
lastScale = [fingers scale]; // Store the previous scale factor for the next pinch gesture call
}
[dict setObject:[NSNumber numberWithFloat:lastScale]
forKey:@"lastScale"];
}
- (void)fingerDidMoveInDraggableView:(UIPanGestureRecognizer *)finger {
NSMutableDictionary *dict = [self dictForViewGuestures:finger];
UIView *viewToDrag = finger.view;
if (finger.state == UIGestureRecognizerStateBegan) {
[dict setObject:[NSValue valueWithCGPoint:viewToDrag.frame.origin]
forKey:@"startDragOffset"];
[dict setObject:[NSValue valueWithCGPoint:[finger locationInView:self.view]]
forKey:@"startDragLocation"];
}
else if (finger.state == UIGestureRecognizerStateChanged) {
NSMutableDictionary *dict = (NSMutableDictionary*) (void*) viewToDrag.tag;
CGPoint stopLocation = [finger locationInView:self.view];
CGPoint startDragLocation = [[dict valueForKey:@"startDragLocation"] CGPointValue];
CGPoint startDragOffset = [[dict valueForKey:@"startDragOffset"] CGPointValue];
CGFloat dx = stopLocation.x - startDragLocation.x;
CGFloat dy = stopLocation.y - startDragLocation.y;
// CGFloat distance = sqrt(dx*dx + dy*dy );
CGRect dragFrame = viewToDrag.frame;
CGSize selfViewSize = self.view.frame.size;
if (!UIDeviceOrientationIsPortrait(self.interfaceOrientation)) {
selfViewSize = CGSizeMake(selfViewSize.height,selfViewSize.width);
}
selfViewSize.width -= dragFrame.size.width;
selfViewSize.height -= dragFrame.size.height;
dragFrame.origin.x = MIN(selfViewSize.width, MAX(0,startDragOffset.x+dx));
dragFrame.origin.y = MIN(selfViewSize.height,MAX(0,startDragOffset.y+dy));
viewToDrag.frame = dragFrame;
}
else if (finger.state == UIGestureRecognizerStateEnded) {
[dict removeObjectForKey:@"startDragLocation"];
[dict removeObjectForKey:@"startDragOffset"];
}
}
-(void) makeView:(UIView*)aView
draggable:(BOOL)draggable
pinchable:(BOOL)pinchable
minPinchScale:(CGFloat)minPinchScale
maxPinchScale:(CGFloat)maxPinchScale{
NSMutableDictionary *dict = (NSMutableDictionary*) (void*) aView.tag;
if (!(pinchable || draggable)) {
if (dict){
[dict release];
aView.tag = 0;
}
return;
}
if (dict) {
UIPanGestureRecognizer *pan =[dict objectForKey:@"UIPanGestureRecognizer"];
if(pan){
if ([aView.gestureRecognizers indexOfObject:pan]!=NSNotFound) {
[aView removeGestureRecognizer:pan];
}
[dict removeObjectForKey:@"UIPanGestureRecognizer"];
}
UIPinchGestureRecognizer *pinch =[dict objectForKey:@"UIPinchGestureRecognizer"];
if(pinch){
if ([aView.gestureRecognizers indexOfObject:pinch]!=NSNotFound) {
[aView removeGestureRecognizer:pinch];
}
[dict removeObjectForKey:@"UIPinchGestureRecognizer"];
}
[dict removeObjectForKey:@"startDragLocation"];
[dict removeObjectForKey:@"startDragOffset"];
[dict removeObjectForKey:@"lastScale"];
[dict removeObjectForKey:@"minScale"];
[dict removeObjectForKey:@"maxScale"];
}
if (draggable) {
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(fingerDidMoveInDraggableView:)];
pan.minimumNumberOfTouches = 1;
pan.maximumNumberOfTouches = 1;
[aView addGestureRecognizer:pan];
[pan release];
dict = [self dictForViewGuestures:pan];
[dict setObject:pan forKey:@"UIPanGestureRecognizer"];
}
if (pinchable) {
CGAffineTransform initialTramsform = CGAffineTransformMakeScale(1.0, 1.0);
aView.transform = initialTramsform;
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(fingersDidPinchInPinchableView:)];
[aView addGestureRecognizer:pinch];
[pinch release];
dict = [self dictForViewGuestures:pinch];
[dict setObject:pinch forKey:@"UIPinchGestureRecognizer"];
[dict setObject:[NSNumber numberWithFloat:minPinchScale] forKey:@"minScale"];
[dict setObject:[NSNumber numberWithFloat:maxPinchScale] forKey:@"maxScale"];
}
}
@end
答案 3 :(得分:3)
大多数其他答案的问题在于他们试图将比例作为线性值处理,而实际上由于UIPinchGestureRecognizer
基于触摸计算其比例属性的方式它是非线性的距离。如果不考虑这一点,用户必须使用更多或更少的夹点距离来“撤消”前一个捏合手势所应用的缩放。
考虑:假设transform.scale
= 1.0
我将手指放在屏幕上6厘米处,然后向内捏3厘米 - 结果gestureRecognizer.scale
为0.5
, 0.5-1.0
为-0.5
,因此transform.scale
将成为1.0+(-0.5)
= 0.5
。现在,我举起手指,将它们放下3厘米,然后向外捏到6厘米。生成的gestureRecognizer.scale
将为2.0
,2.0-1.0
为1.0
,因此transform.scale
将成为0.5+1.0
= 1.5
。不是我想要发生的事情。
修复方法是将delta夹点比例计算为其先前值的比例。我将手指向下移动6厘米,并向内捏3厘米,因此gestureRecognizer.scale
为0.5
。 0.5/1.0
为0.5
,因此我的新transform.scale
为1.0*0.5
= 0.5
。接下来,我将手指放下3厘米,向外捏到6厘米。 gestureRecognizer.scale
为2.0
,2.0/1.0
为2.0
,因此我的新transform.scale
为0.5*2.0
= 1.0
,这正是我想要发生什么。
这是代码:
-(void)viewDidLoad
中的:
self.zoomGestureCurrentZoom = 1.0f;
-(void)onZoomGesture:(UIPinchGestureRecognizer*)gestureRecognizer
中的:
if ( gestureRecognizer.state == UIGestureRecognizerStateBegan )
{
self.zoomGestureLastScale = gestureRecognizer.scale;
}
else if ( gestureRecognizer.state == UIGestureRecognizerStateChanged )
{
// we have to jump through some hoops to clamp the scale in a way that makes the UX intuitive
float scaleDeltaFactor = gestureRecognizer.scale/self.zoomGestureLastScale;
float currentZoom = self.zoomGestureCurrentZoom;
float newZoom = currentZoom * scaleDeltaFactor;
// clamp
float kMaxZoom = 4.0f;
float kMinZoom = 0.5f;
newZoom = MAX(kMinZoom,MIN(newZoom,kMaxZoom));
self.view.transform = CGAffineTransformScale([[gestureRecognizer view] transform], newZoom, newZoom);
// store for next time
self.zoomGestureCurrentZoom = newZoom;
self.zoomGestureLastScale = gestureRecognizer.scale;
}
答案 4 :(得分:2)
非常感谢,非常有用的代码片段,可以达到最小和最大范围。
我发现当我首先使用:
翻转视图时CGAffineTransformScale(gestureRecognizer.view.transform, -1.0, 1.0);
缩放视图时会导致闪烁。
让我知道你的想法,但我的解决方案是更新上面的代码示例,如果视图已被翻转(通过属性设置标志),则反转比例值:
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged)
{
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:@"transform.scale"] floatValue];
if(self.isFlipped) // (inverting)
{
currentScale *= -1;
}
CGFloat newScale = 1 - (self.lastScale - [gestureRecognizer scale]);
newScale = MIN(newScale, self.maximumScaleFactor / currentScale);
newScale = MAX(newScale, self.minimumScaleFactor / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
gestureRecognizer.view.transform = transform;
self.lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
答案 5 :(得分:1)
这里提到的其他方法对我来说不起作用,但是从先前的答案和(在我看来)简化事情中得到一些东西,我已经让它为我工作了。 effectiveScale
是viewDidLoad
中设置为1.0的ivar。
-(void)zoomScale:(UIPinchGestureRecognizer *)recognizer
{
if([recognizer state] == UIGestureRecognizerStateEnded) {
// Reset last scale
lastScale = 1.0;
return;
}
if ([recognizer state] == UIGestureRecognizerStateBegan ||
[recognizer state] == UIGestureRecognizerStateChanged) {
CGFloat pinchscale = [recognizer scale];
CGFloat scaleDiff = pinchscale - lastScale;
if (scaleDiff < 0)
scaleDiff *= 2; // speed up zoom-out
else
scaleDiff *= 0.7; // slow down zoom-in
effectiveScale += scaleDiff;
// Limit scale between 1 and 2
effectiveScale = effectiveScale < 1 ? 1 : effectiveScale;
effectiveScale = effectiveScale > 2 ? 2 : effectiveScale;
// Handle transform in separate method using new effectiveScale
[self makeAndApplyAffineTransform];
lastScale = pinchscale;
}
}
答案 6 :(得分:1)
- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer{
//recognizer.scale=1;
CGFloat pinchScale = recognizer.scale;
pinchScale = round(pinchScale * 1000) / 1000.0;
NSLog(@"%lf",pinchScale);
if (pinchScale < 1)
{
currentLabel.font = [UIFont fontWithName:currentLabel.font.fontName size:
(currentLabel.font.pointSize - pinchScale)];
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
[currentLabel sizeToFit];
recognizer.scale=1;
}
else
{
currentLabel.font = [UIFont fontWithName:currentLabel.font.fontName size:(currentLabel.font.pointSize + pinchScale)];
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
[currentLabel sizeToFit];
recognizer.scale=1;
}
//currentLabel.adjustsFontSizeToFitWidth = YES;
// [currentLabel sizeToFit];
NSLog(@"Font :%@",label.font);
}
答案 7 :(得分:1)
gestureRecognizer.scale
在捏的开始处以1.0开始(gestureRecognizer.state == .began),而处于更高状态(.changed或.end)的gestureRecognizer.scale
为始终以此为依据,例如,如果在缩放开始时视图大小为view_size
(可能与原始大小orig_view_size
不同),则gestureRecognizer.scale
总是以1.0开始,如果以后变成2.0,则其大小将为2 * view_size
,因此缩放总是基于缩放开始时的缩放。
我们可以在捏紧开始时获得标度(gestureRecognizer.state == .begin) lastScale = self.imageView.frame.width/self.imageView.bounds.size.width
,因此现在原始图像的标度应为{{1} }
lastScale * gestureRecognizer.scale
:上一轮Pinch的缩放比例,一轮Pinch是从state.start到state.end,并且缩放比例基于原始视图的大小。
lastScale
:当前比例,基于上一撮Pinch后的视图大小。
gestureRecognizer.scale
:当前比例,基于原始视图的大小。
currentScale
:基于原始视图尺寸的新比例。 newScale
,您可以通过将限制与newScale = lastScale * gestureRecognizer.scale
进行比较来限制视图的比例。
```
newScale
```
var lastScale:CGFloat = 1.0
@objc func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) {
var newScale = gestureRecognizer.scale
if gestureRecognizer.state == .began {
lastScale = self.imageView.frame.width/self.imageView.bounds.size.width
}
newScale = newScale * lastScale
if newScale < minScale {
newScale = minScale
} else if newScale > maxScale {
newScale = maxScale
}
let currentScale = self.imageView.frame.width/self.imageView.bounds.size.width
self.imageView.transform = CGAffineTransform(scaleX: newScale, y: newScale)
print("last Scale: \(lastScale), current scale: \(currentScale), new scale: \(newScale), gestureRecognizer.scale: \(gestureRecognizer.scale)")
}
在每个Pinch通知中以1.0开头,这要求您在每个通知处理程序末尾的代码中重置gestureRecognizer.scale
,因此现在gestureRecognizer.scale = 1
是基于上一次“捏”通知的视图大小,不是基于捏捏开始时的视图大小。这是方法1最重要的区别。由于我们不依赖上一轮的规模,因此不再需要gestureRecognizer.scale
。
lastScale
:当前比例,基于原始视图的大小。
currentScale
:新的缩放比例,基于上一捏(而非最后一轮)的视图尺寸,基于原始视图尺寸的缩放比例值为{{ 1}}
我们现在使用gestureRecognizer.scale
,它使用的缩放比例基于上一撮(不是上一轮)的视图大小。
```
currentScale * gestureRecognizer.scale
```
答案 8 :(得分:0)
我采用了@Paul Solt 解决方案 - 顺便说一句,这很棒,并将其改编为 Swift,对于那些感兴趣的人
@objc func pinchUpdated(recognizer: UIPinchGestureRecognizer) {
if recognizer.state == .began {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = recognizer.scale
}
if recognizer.state == .began || recognizer.state == .changed {
let currentScale = recognizer.view!.layer.value(forKeyPath: "transform.scale") as! CGFloat
// Constants to adjust the max/min values of zoom
let maxScale: CGFloat = 4.0
let ninScale: CGFloat = 0.9
var newScale: CGFloat = 1 - (lastScale - recognizer.scale)
newScale = min(newScale, maxScale / currentScale)
newScale = max(newScale, ninScale / currentScale)
recognizer.view!.transform = recognizer.view!.transform.scaledBy(x: newScale, y: newScale)
lastScale = recognizer.scale // Store the previous scale factor for the next pinch gesture call
}
}
答案 9 :(得分:-1)
您可以使用滚动视图吗?然后你可以使用scrollView.minimumZoomScale和scrollView.maximumZoomScale