it-roy-ru.com

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

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

}

39
VinnyD

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

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

18
Anomie

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

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

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

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

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

Вы можете сделать любое подпредставление в контроллерах представления «представлением» перетаскиваемым или сжимаемым (или обоими) Поместите одну строку кода в ваш 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
4
unsynchronized

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

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

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

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

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

Способ 1

gestureRecognizer.scale начинаться с 1.0 в начале пинча (gestRecognizer.state == .began) , а gestureRecognizer.scale в более позднем состоянии (.changed или .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, раунд Pinch - от state.start до state.end, а масштаб основан на исходном размере представления.

  • gestureRecognizer.scale: текущий масштаб, основанный на размере представления после последнего раунда Pinch.

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