Comment pourrais-je limiter l'échelle de UIPinchGestureRecognizer à un niveau minimum et maximum? La propriété scale ci-dessous semble être relative à la dernière échelle connue (le delta du dernier état) et je ne vois pas comment définir une limite à la taille/hauteur de l'objet zoomé.
-(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];
}
Il n'y a pas moyen de limiter l'échelle sur une UIPinchGestureRecognizer
. Pour limiter la hauteur dans votre code, vous devriez pouvoir faire quelque chose comme ceci:
CGFloat scale = 1.0 - (lastScale - pinchscale);
CGRect bounds = [(UIPinchGestureRecognizer*)sender view].bounds;
scale = MIN(scale, maximumHeight / CGRectGetHeight(bounds));
scale = MAX(scale, minimumHeight / CGRectGetHeight(bounds));
Pour limiter la largeur, remplacez "Hauteur" par "Largeur" dans les deux dernières lignes.
Voici la solution que j'ai trouvée après avoir utilisé la réponse d'Anomie comme point de départ.
- (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
}
}
J'ai pris quelques informations tirées des réponses de Paul Solt et Anoime, et j'ai ajouté cela à une catégorie existante que j'ai créée pour UIViewController afin de permettre de rendre n'importe quel UIView déplaçable, pour le rendre maintenant utilisable par gestes et transformations.
Remarque: ceci détache la propriété de la balise de la vue que vous êtes en train de faire glisser/pincer. Ainsi, si vous avez besoin de la balise pour autre chose, vous pouvez envisager de placer cette valeur dans le NSMutableDictionary utilisé par cette technique. C'est disponible en tant que [self dictForView: theView]
Mise en œuvre dans votre projet:
Vous pouvez rendre n'importe quelle sous-vue dans les contrôleurs de vue "vue" déplaçable ou pincable (ou les deux) Placer une seule ligne de code dans votre viewDidLoad (par exemple :)
[self makeView:mySubView draggable:YES pinchable:YES minPinchScale:0.75 maxPinchScale:1.0];
désactivez-le dans viewDidUnload (publie guestures & dictionary):
[self makeView:mySubView draggable:NO pinchable:NO minPinchScale:1.0 maxPinchScale:1.0];
Fichier 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
Fichier 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
Le problème avec la plupart des autres réponses est qu’elles essaient de traiter l’échelle comme une valeur linéaire, alors qu’elle est non linéaire en raison de la façon dont UIPinchGestureRecognizer
calcule sa propriété d’échelle en fonction de la distance au toucher. Lorsque cela n'est pas pris en compte, l'utilisateur doit utiliser une distance de pincement plus ou moins grande pour «annuler» la mise à l'échelle appliquée par un geste de pincement précédent.
Considérons: supposons que transform.scale
= 1.0
et que je place mes doigts à 6 cm de l’écran, puis que je resserre à 3 cm de distance - le gestureRecognizer.scale
résultant est 0.5
et 0.5-1.0
est -0.5
, de sorte que transform.scale
deviendra 1.0+(-0.5)
= 0.5
. Maintenant, je lève mes doigts, les redispose de 3 cm et les pince à 6 cm. Le gestureRecognizer.scale
résultant sera 2.0
, et 2.0-1.0
sera 1.0
, donc transform.scale
deviendra 0.5+1.0
= 1.5
. Pas ce que je voulais arriver.
La solution consiste à calculer l'échelle de pincement delta en proportion de sa valeur précédente. Je place mes doigts à 6 cm de distance et pince à 3 cm, donc gestureRecognizer.scale
est 0.5
. 0.5/1.0
est 0.5
, mon nouveau transform.scale
est donc 1.0*0.5
= 0.5
. Ensuite, je place mes doigts à 3 cm de distance et pince à 6 cm vers l’extérieur. gestureRecognizer.scale
est alors 2.0
, et 2.0/1.0
est 2.0
; mon nouveau transform.scale
est donc 0.5*2.0
= 1.0
, ce qui est exactement ce que je souhaitais.
Ici c'est en code:
dans -(void)viewDidLoad
:
self.zoomGestureCurrentZoom = 1.0f;
dans -(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;
}
Merci, extrait de code vraiment utile au-dessus du serrage à une échelle minimale et maximale.
J'ai trouvé que lorsque j'ai retourné la vue d'abord en utilisant:
CGAffineTransformScale(gestureRecognizer.view.transform, -1.0, 1.0);
cela provoquerait un scintillement lors de la mise à l'échelle de la vue.
Faites-moi savoir ce que vous pensez, mais la solution pour moi était de mettre à jour l'exemple de code ci-dessus. Si la vue a été retournée (indicateur défini via la propriété), inversez la valeur d'échelle:
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
Les autres approches mentionnées ici ne fonctionnaient pas pour moi, mais prenons quelques éléments des réponses précédentes et (à mon avis) simplifient les choses. effectiveScale
est un ivar défini sur 1.0 dans viewDidLoad
.
-(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
commence par 1.0 au début du pincement (gestureRecognizer.state == .began), et gestureRecognizer.scale
dans un état ultérieur (.changed ou .end) est toujours basé sur celui-ci, par exemple, si la taille d'affichage est view_size
au début du pincement (peut ne pas être identique à la taille originale orig_view_size
), gestureRecognizer.scale
commence toujours par 1.0, et s'il devient 2.0 plus tard, sa taille sera 2 * view_size
, donc la balance sera toujours basée sur celle au début du pincement.
Et nous pouvons obtenir l'échelle au début du pincement (gestureRecognizer.state == .began)lastScale = self.imageView.frame.width/self.imageView.bounds.size.width
, de sorte que l'échelle de l'image d'origine doit maintenant être lastScale * gestureRecognizer.scale
lastScale
: L'échelle du dernier tour de Pinch, un tour de pincement va de state.start à state.end, et l'échelle est basée sur la taille de la vue d'origine.
gestureRecognizer.scale
: échelle actuelle, basée sur la taille de la vue après le dernier tour de pincement.
currentScale
: échelle actuelle, basée sur la taille de la vue originale.
newScale
: nouvelle échelle, basée sur la taille de la vue orignial. newScale = lastScale * gestureRecognizer.scale
, et vous pouvez limiter l’échelle de la vue en comparant la limitation à 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
commencez par 1.0 avec chaque notification de pincement, cela nécessite de réinitialiser gestureRecognizer.scale = 1
dans le code à la fin de chaque gestionnaire de notification, de sorte que gestureRecognizer.scale
est maintenant basé sur la taille de la dernière notification de pincement, NOT en fonction de la vue taille au début de pincement. C'est la différence la plus importante avec la méthode 1. Et comme nous ne comptons pas sur l'échelle du dernier tour, nous n'avons plus besoin de lastScale
.
currentScale
: échelle actuelle, basée sur la taille de la vue originale.
gestureRecognizer.scale
: nouvelle échelle, basée sur la taille de la vue du dernier pincement (pas du dernier tour), la valeur de l'échelle basée sur la taille de la vue originale sera currentScale * gestureRecognizer.scale
Et nous utilisons maintenant transform.scaledBy
, qui utilise l'échelle basée sur la taille de view du dernier pincement (pas le dernier tour).
`` `
@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;
}
}