Максимальная / минимальная шкала масштабирования Увеличение UIPinchGestureRecognizer - iPhone iOS - PullRequest
42 голосов
/ 01 марта 2011

Как я могу ограничить масштаб 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];

}

Ответы [ 10 ]

106 голосов
/ 27 марта 2011

Вот решение, которое я выяснил после использования ответа Аноми в качестве отправной точки.

- (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  
    }
}
18 голосов
/ 01 марта 2011

Нет способа ограничить масштаб на 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));

Чтобы ограничить ширину, измените «Высота» на «Ширина» в последних двух строках.

4 голосов
/ 27 сентября 2011

Я взял некоторую информацию, полученную из ответов Пола Солта и Анойма, и добавил, что в существующую категорию, которую я сделал для UIViewController, можно сделать перетаскиваемый любой UIView, чтобы теперь его можно было сжимать с помощью жестов и преобразований.

Примечание: это загрязняет свойство тега представления, которое вы делаете перетаскиваемым / сжимаемым. Так что если вам нужен тег для чего-то другого, вы можете рассмотреть возможность размещения этого значения в NSMutableDictionary, используемом этим методом. Это доступно как [self dictForView: theView]

Реализация в вашем проекте:

Вы можете сделать любое подпредставление в контроллере представления "view" перетаскиваемым или сжимаемым (или оба) поместите одну строку кода в ваш 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 голосов
/ 20 января 2014

Проблема с большинством других ответов заключается в том, что они пытаются работать со шкалой как с линейным значением, когда на самом деле оно нелинейное из-за способа, которым 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.Не то, что я хотел, чтобы произошло.

Исправление заключается в том, чтобы вычислить шкалу дельта-пинча как пропорцию от ее предыдущего значения.Я опускаю пальцы на 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;
}
2 голосов
/ 08 октября 2012

Спасибо, действительно полезный фрагмент кода выше ограничения до минимального и максимального масштаба.

Я обнаружил, что, когда я сначала переворачиваю вид, используя:

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
1 голос
/ 19 августа 2018

Метод 1

gestureRecognizer.scale начинаться с 1,0 в начале пинча (жестRecognizer.state == .began) и gestureRecognizer.scale в более позднем состоянии (. Изменен или.end) всегда основывается на этом, например, если размер представления view_size в начале пинча (может не совпадать с исходным размером orig_view_size), gestureRecognizer.scale всегда начинается с 1.0, и если онстановится 2.0 позже, его размер будет 2 * view_size, поэтому масштаб всегда основан на том, когда начинается пинч.

И мы можем получить шкалу в начале пинча (жестRecognizer.state ==.began) lastScale = self.imageView.frame.width/self.imageView.bounds.size.width, поэтому масштаб исходного изображения теперь должен быть lastScale * gestureRecognizer.scale

  • lastScale: масштаб последнего раунда Pinch, раундПинч - от 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)")
}

` ``

Метод 2

gestureRecognizer.scale начинаться с 1,0 при каждом уведомлении Pinch , для этого необходимо сбросить gestureRecognizer.scale = 1 в коде в конце каждого обработчика уведомлений, так что теперьgestureRecognizer.scale основан на размере представления последнего уведомления Pinch, НЕ основан на размере представления в начале пинча .Это самое важное отличие от метода 1. А поскольку мы не полагаемся на масштаб последнего раунда, нам больше не нужно lastScale.

  • currentScale: текущая шкала, основанная на размерах орбины.

  • gestureRecognizer.scale: новая шкала, основанная на размер представления последнего Pinch (не последнего раунда) , значение масштаба, основанное на размерах исходного изображения, будет currentScale * gestureRecognizer.scale

И мы используем transform.scaledBy сейчас,которые используют шкалу, основанную на размере представления последнего Pinch (не последнего раунда) .

`` `

@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 голос
/ 28 мая 2014
- (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 голос
/ 09 января 2013

Другие подходы, упомянутые здесь, не работали для меня, но, взяв пару вещей из предыдущих ответов и (на мой взгляд) упрощающих вещей, я заставил это работать на меня.effectiveScale - это значение ивара, установленное на 1,0 в 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;
    }
}
0 голосов
/ 05 декабря 2016
- (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 голосов
/ 01 марта 2011

Можете ли вы использовать вместо этого вид прокрутки?Тогда вы можете использовать scrollView.minimumZoomScale и scrollView.maximumZoomScale

...