UIPinchGestureRecognizerのスケールを最小および最大レベルに制限するにはどうすればよいですか?以下のスケールプロパティは、最後の既知のスケール(最後の状態からのデルタ)に関連しているようであり、ズームするオブジェクトのサイズ/高さに制限を設定する方法がわかりません。
-(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];
}
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行で「高さ」を「幅」に変更します。
以下は、アノミーの答えを出発点として使用した後に見つけた解決策です。
- (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
}
}
Paul SoltとAnoimeの回答から収集したいくつかの情報を取得し、それをUIViewController用に作成した既存のカテゴリに追加して、任意のUIViewをドラッグ可能にし、ジェスチャーとトランスフォームを使用してピンチできるようにしました。
注:これは、ドラッグ可能/ピンチ可能にするビューのタグプロパティを汚します。そのため、他の何かのためにタグが必要な場合、この手法で使用されているNSMutableDictionaryにその値を配置することを検討できます。 [self dictForView:theView]として利用可能です
プロジェクトに実装する:
View Controller内の任意のサブビューを「表示」ドラッグ可能またはピンチ可能(またはその両方)にすると、viewDidLoadに1行のコードを配置できます(例:)
[self makeView:mySubView draggable:YES pinchable:YES minPinchScale:0.75 maxPinchScale:1.0];
viewDidUnloadでオフにします(guesturesと辞書を解放します):
[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
他のほとんどの答えの問題は、実際にUIPinchGestureRecognizer
がタッチ距離に基づいてそのスケールプロパティを計算する方法のために非線形であるにもかかわらず、スケールを線形値として処理しようとしていることです。これが考慮されていない場合、ユーザーは以前のピンチジェスチャによって適用されたスケーリングを「元に戻す」ために、多少のピンチ距離を使用する必要があります。
考えてみましょう:_transform.scale
_ = _1.0
_と仮定し、画面上で指を6cm離してから、内側に3cm離します-結果の_gestureRecognizer.scale
_は_0.5
_、および_0.5-1.0
_は_-0.5
_なので、_transform.scale
_は1.0+(-0.5)
= _0.5
_になります。次に、指を持ち上げて、3cm離れた位置に戻し、外側に6cmまでピンチします。結果の_gestureRecognizer.scale
_は_2.0
_になり、_2.0-1.0
_は_1.0
_になるため、_transform.scale
_は_0.5+1.0
_ = _1.5
_になります。私が望んでいたことではありません。
修正方法は、デルタピンチスケールを以前の値の割合として計算することです。指を6cm離し、内側に3cmにピンチします。したがって、_gestureRecognizer.scale
_は_0.5
_です。 _0.5/1.0
_は_0.5
_なので、新しい_transform.scale
_は_1.0*0.5
_ = _0.5
_です。次に、指を3cm離して置き、外側に6cmにピンチします。 _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;
}
_
おかげで、最小および最大のスケールにクランプする上で非常に便利なコードスニペットができました。
私は最初にビューを反転させたときに次を使用することがわかりました:
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
ここで言及した他のアプローチは私にはうまくいきませんでしたが、以前の回答からいくつかのことを取り、(私の意見では)物事を単純化して、私はこれを私のために働かせました。 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;
}
}
gestureRecognizer.scale
ピンチの始めに1.0で始まる(gestureRecognizer.state == .began)、およびgestureRecognizer.scale
後の状態(.changedまたは.end)では、たとえば、ビューサイズがview_size
ピンチの先頭(元のサイズと同じではない場合がありますorig_view_size
)、gestureRecognizer.scale
は常に1.0で始まり、後で2.0になれば、サイズは2 * view_size
。したがって、スケールは常にピンチ開始時のスケールに基づきます。
そして、スケールを取得できますピンチの開始時(gestureRecognizer.state == .began)lastScale = self.imageView.frame.width/self.imageView.bounds.size.width
ので、元の画像のスケールはlastScale * gestureRecognizer.scale
lastScale
:ピンチの最後のラウンドのスケール、ピンチのラウンドはstate.startからstate.endであり、スケールは元のビューサイズに基づいています。
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)")
}
`` `
gestureRecognizer.scale
各ピンチ通知で1.0から開始、これにはリセットが必要ですgestureRecognizer.scale = 1
は各通知ハンドラの最後のコードにあるため、今はgestureRecognizer.scale
は、最後のピンチ通知のビューサイズに基づいています。ピンチの開始時のビューサイズに基づいていません。これは、方法1との最も重要な違いです。また、最終ラウンドのスケールに依存しないため、lastScale
はもう必要ありません。
currentScale
:元のビューサイズに基づく現在のスケール。
gestureRecognizer.scale
:最後のピンチ(最後のラウンドではない)のビューサイズに基づく新しいスケール、元のビューサイズに基づくスケール値はcurrentScale * gestureRecognizer.scale
そして、transform.scaledBy
現在、最後のピンチ(最後のラウンドではなく)の表示サイズに基づくスケールを使用しています。
`` `
@objc func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) {
let currentScale = self.imageView.frame.width/self.imageView.bounds.size.width
var newScale = gestureRecognizer.scale
if currentScale * gestureRecognizer.scale < minScale {
newScale = minScale / currentScale
} else if currentScale * gestureRecognizer.scale > maxScale {
newScale = maxScale / currentScale
}
self.imageView.transform = self.imageView.transform.scaledBy(x: newScale, y: newScale)
print("current scale: \(currentScale), new scale: \(newScale)")
gestureRecognizer.scale = 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);
}
- (void)pinchToZoom:(UIPinchGestureRecognizer*)gesture
{
switch (gesture.state)
{
case UIGestureRecognizerStateBegan:
{
lastScale = gesture.scale;
}break;
case UIGestureRecognizerStateChanged:
{
const CGFloat zoomSensitivity = 5;
const CGFloat zoomMin = 1;
const CGFloat zoomMax = 16;
CGFloat objectScale = gesture.view.contentScaleFactor;
CGFloat zoomDiff = lastScale - gesture.scale;
CGFloat zoomDirty = objectScale - zoomDiff * zoomSensivity;
CGFloat zoomTo = fmaxf(zoomMin, fminf(zoomDirty, zoomMax));
// step round if needed (neutralize elusive changes)
zoomTo = (NSInteger)(zoomTo * 10) * 0.1;
if ( objectScale != zoomTo )
gesture.view.contentScaleFactor = zoomTo;
lastScale = gesture.scale;
}break;
default:
break;
}
}