web-dev-qa-db-fra.com

Échelle de pincement maximum/minimum Zoom in Reconnaissance UIPinchGesture - iPhone iOS

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];

}

39
VinnyD

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.

18
Anomie

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  
    }
}
104
Paul Solt

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
4
unsynchronized

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;
}
3
damian

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
2
Shagun

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;
    }
}
1
Kevin_TA

Méthode 1

gestureRecognizer.scalecommence 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)")
}

`` `

Méthode 2

gestureRecognizer.scalecommencez 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
}

`` `

1
Fu Jiantao
- (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);
}
1
Arvind Kumar
- (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;
    }
}
0
Roman Solodyashkin