it-swarm.com.de

Max/Min-Skala des Pinch-Zooms in UIPinchGestureRecognizer - iPhone iOS

Wie kann ich den Maßstab des UIPinchGestureRecognizer auf ein Minimum und ein Maximum beschränken? Die unten angegebene Skalierungseigenschaft scheint relativ zu der letzten bekannten Skala zu sein (das Delta aus dem letzten Zustand), und ich kann nicht herausfinden, wie ich die Größe/Höhe des zu zoomenden Objekts begrenzen kann.

-(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

Es gibt keine Möglichkeit, die Skalierung auf UIPinchGestureRecognizer zu beschränken. Um die Höhe in Ihrem Code zu begrenzen, sollten Sie Folgendes tun können:

CGFloat scale = 1.0 - (lastScale - pinchscale);
CGRect bounds = [(UIPinchGestureRecognizer*)sender view].bounds;
scale = MIN(scale, maximumHeight / CGRectGetHeight(bounds));
scale = MAX(scale, minimumHeight / CGRectGetHeight(bounds));

Um die Breite zu begrenzen, ändern Sie 'Height' in 'Breite' in den letzten beiden Zeilen.

18
Anomie

Hier ist die Lösung, die ich herausgefunden habe, nachdem ich Anomies Antwort als Ausgangspunkt genommen hatte.

- (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

Ich nahm einige Informationen aus den Antworten von Paul Solt und Anoime und fügte diese zu einer bestehenden Kategorie hinzu, die ich für UIViewController erstellt habe, um zu ermöglichen, dass alle UIView-Objekte in die gewünschte Richtung gezogen werden können. 

Hinweis: Dies verschmutzt die Tag-Eigenschaft der Ansicht, die Sie ziehen/kneifen können. Wenn Sie also das Tag für etwas anderes benötigen, können Sie diesen Wert in das NSMutableDictionary aufnehmen, das von dieser Technik verwendet wird. Das ist verfügbar als [self dictForView: theView]

Umsetzung in Ihrem Projekt:

Sie können eine beliebige Unteransicht innerhalb der View-Ansicht "view" ziehen und/oder quetschbar machen (oder beides). Platzieren Sie eine einzelne Codezeile in Ihrem viewDidLoad (zum Beispiel :).

[self makeView:mySubView draggable:YES pinchable:YES minPinchScale:0.75 maxPinchScale:1.0];

deaktivieren Sie es in viewDidUnload (veröffentlicht guestures & dictionary):

[self makeView:mySubView draggable:NO pinchable:NO minPinchScale:1.0 maxPinchScale:1.0];

DragAndPinchScale.h-Datei

#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-Datei

#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

Das Problem bei den meisten anderen Antworten ist, dass sie versuchen, die Skalierung als linearen Wert zu behandeln, obwohl sie aufgrund der Art und Weise, wie UIPinchGestureRecognizer ihre Skalierungseigenschaft basierend auf der Berührungsentfernung berechnet, tatsächlich nicht linear ist. Wenn dies nicht berücksichtigt wird, muss der Benutzer mehr oder weniger Kneifabstand verwenden, um die durch eine vorherige Kneifbewegung angewendete Skalierung rückgängig zu machen.

Bedenken Sie: Angenommen, transform.scale = 1.0 und ich platziere meine Finger in einem Abstand von 6 cm auf dem Bildschirm und drücke sie dann in einem Abstand von 3 cm nach innen - der resultierende gestureRecognizer.scale ist 0.5 und 0.5-1.0 ist -0.5, daher wird transform.scale zu 1.0+(-0.5) = 0.5. Jetzt hebe ich meine Finger, lege sie wieder 3 cm auseinander und drücke sie auf 6 cm nach außen. Der resultierende gestureRecognizer.scale ist 2.0, und 2.0-1.0 ist 1.0, sodass transform.scale zu 0.5+1.0 = 1.5. Nicht das, was ich wollte.

Der Fix besteht darin, die Delta-Pinch-Skala als Anteil ihres vorherigen Werts zu berechnen. Ich lege meine Finger in einem Abstand von 6 cm nach unten und drücke sie auf 3 cm nach innen, sodass gestureRecognizer.scale0.5 ist. 0.5/1.0 ist 0.5, also ist mein neuer transform.scale1.0*0.5 = 0.5. Als Nächstes lege ich meine Finger in einem Abstand von 3 cm nach unten und drücke sie auf 6 cm nach außen. gestureRecognizer.scale ist dann 2.0 und 2.0/1.0 ist 2.0, also ist mein neuer transform.scale0.5*2.0 = 1.0, das ist genau das, was ich passieren wollte.

Hier ist es im Code:

in -(void)viewDidLoad:

self.zoomGestureCurrentZoom = 1.0f;

in -(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

Vielen Dank, wirklich nützliches Code-Snippet über dem Klemmen auf ein Minimum und Maximum.

Ich fand das, als ich die Ansicht zuerst umgedreht hatte:

CGAffineTransformScale(gestureRecognizer.view.transform, -1.0, 1.0); 

dies würde beim Skalieren der Ansicht zu einem Flimmern führen.

Lassen Sie mich wissen, was Sie denken, aber die Lösung bestand für mich darin, das obige Codebeispiel zu aktualisieren. Wenn die Ansicht umgedreht wurde (Flag über Eigenschaft gesetzt), dann invertieren Sie den Skalierungswert:

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

Andere Ansätze, die hier erwähnt wurden, haben für mich nicht funktioniert, aber ich habe ein paar Dinge aus früheren Antworten genommen und (meiner Meinung nach) die Dinge vereinfacht, ich habe dies für mich erledigt. effectiveScale ist eine in viewDidLoad auf 1,0 gesetzte 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;
    }
}
1
Kevin_TA

Methode 1

gestureRecognizer.scale Beginnen Sie mit 1.0 am Anfang der Änderung (gestureRecognizer.state == .began) , und gestureRecognizer.scale im späteren Zustand (.changed oder .end) basiert immer darauf, z. B. wenn die Ansichtsgröße view_size ist. am Anfang der Quetschung (möglicherweise nicht mit der Originalgröße orig_view_size identisch), beginnt gestureRecognizer.scale immer mit 1.0, und wenn später 2.0 wird, wird die Größe 2 * view_size sein. Die Skala basiert also immer darauf, wenn die Quetschung beginnt.

Und wir können die Skala am Anfang von Pinch erhalten (gestureRecognizer.state == .began) lastScale = self.imageView.frame.width/self.imageView.bounds.size.width, daher sollte die Skala des Originalbildes jetzt lastScale * gestureRecognizer.scale sein.

  • lastScale: Die Skala der letzten Runde von Pinch, eine Pinch-Runde, ist von state.start bis state.end und die Skala basiert auf der ursprünglichen Ansichtsgröße.

  • gestureRecognizer.scale: aktueller Maßstab, basierend auf der Ansichtsgröße nach der letzten Pinch-Runde.

  • currentScale: aktueller Maßstab, basierend auf der Größe der ursprünglichen Ansicht.

  • newScale: neuer Maßstab basierend auf der orignial-Ansichtsgröße . newScale = lastScale * gestureRecognizer.scale, und Sie können den Maßstab der Ansicht einschränken, indem Sie die Einschränkung mit newScale vergleichen.

`` `

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)")
}

`` `

Methode 2

gestureRecognizer.scale Beginnen Sie bei jeder Pinch-Benachrichtigung mit 1.0. Sie müssen gestureRecognizer.scale = 1 im Code am Ende jedes Benachrichtigungshandlers zurücksetzen. Daher basiert gestureRecognizer.scale auf der Ansichtsgröße der letzten Pinch-Benachrichtigung. NICHT auf der Ansicht Größe am Anfang der Prise . Dies ist der wichtigste Unterschied zu Methode 1. Da wir uns nicht auf die Skala der letzten Runde verlassen, brauchen wir lastScale nicht mehr. 

  • currentScale: aktueller Maßstab, basierend auf der Größe der ursprünglichen Ansicht.

  • gestureRecognizer.scale: neue Skala, basierend auf der Ansichtsgröße der letzten Pinch (nicht der letzten Runde) , der auf der ursprünglichen Ansichtsgröße basierende Skalenwert ist currentScale * gestureRecognizer.scale

Und wir verwenden jetzt transform.scaledBy, der die Skala basierend auf Ansichtsgröße der letzten Pinch (nicht der letzten Runde) verwendet.

`` `

@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