简体   繁体   English

是否有手势识别器可同时处理夹点和平移?

[英]Is there a gesture recognizer that handles both pinch and pan together?

So I am working with the iOS 4.2 to add zoom and pan to my application. 所以我正在使用iOS 4.2来为我的应用程序添加缩放和平移。 I have implemented an instance of the UIPinchGestureRecognizer and UIPanGestureRecognizer. 我已经实现了UIPinchGestureRecognizer和UIPanGestureRecognizer的实例。 It seems to me that only one of these is recognizing a gesture at a time. 在我看来,其中只有一个是一次识别一个手势。 In particular, the latter only reacts when one finger is down, while the former reacts when the second finger is present. 特别地,后者仅在一个手指向下时作出反应,而前者在第二手指存在时作出反应。 That is okay, but it has some side effects that I think make for inferior quality of user experience. 这没关系,但它有一些副作用,我认为这会产生低劣的用户体验质量。

When you put two fingers down and then move one of them, the image expands (zooms in) like it should, but the pixels under the fingers are no longer under the finger. 当您放下两根手指然后移动其中一根手指时,图像会像应该的那样展开(放大),但手指下方的像素不再位于手指下方。 The image scales from the center of the image, not the mid point between the two fingers. 图像从图像中心缩放,而不是两个手指之间的中点。 And that center point is itself moving. 而这个中心点本身就在发展。 I want that center point's movement to dictate the panning of the image overall. 我希望中心点的运动能够决定整体图像的平移。

Do nearly all iOS applications have this same behavior, where the image zooms in or out around the center of the image rather than the pixels under the fingers tracking the fingers? 几乎所有iOS应用程序都有相同的行为,图像放大或缩小图像中心周围而不是手指跟踪手指下方的像素?

It seems to me that creating a custom gesture recognizer is the correct design approach to this problem, but it also seems to me that someone would have created such a recognizer for commercially free download and use. 在我看来,创建一个自定义手势识别器是解决这个问题的正确设计方法,但在我看来,有人会创建这样的识别器,以便商业免费下载和使用。 Is there such a UIGestureRecognizer? 有没有这样的UIGestureRecognizer?

Sorry, in a rush but this is the code I used for one of my demo apps, it can pinch zoom and pan at the same time without using scrollview. 抱歉,这是我用于其中一个演示应用程序的代码,它可以同时捏缩放和平移,而无需使用scrollview。

Don't forget to conform to UIGestureRecognizerDelegate protocol 不要忘记遵守UIGestureRecognizerDelegate协议

If you're not able to get both pinch and pan at the same time, maybe it's because you're missing this method: 如果你无法同时获得捏和平移,也许是因为你错过了这种方法:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

Here is the full source code: 这是完整的源代码:

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    isEditing = false;

    photoView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
    [photoView setImage:[UIImage imageNamed:@"photo.png"]];
    photoView.hidden = YES;

    maskView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
    [maskView setImage:[UIImage imageNamed:@"maskguide.png"]];
    maskView.hidden = YES;

    displayImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];

    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];

    [panGesture setDelegate:self];
    [pinchGesture setDelegate:self];

    [photoView addGestureRecognizer:panGesture];
    [photoView addGestureRecognizer:pinchGesture];
    [photoView setUserInteractionEnabled:YES];

    [panGesture release];
    [pinchGesture release];

    btnEdit = [[UIButton alloc] initWithFrame:CGRectMake(60, 400, 200, 50)];
    [btnEdit setBackgroundColor:[UIColor blackColor]];
    [btnEdit setTitle:@"Start Editing" forState:UIControlStateNormal];
    [btnEdit addTarget:self action:@selector(toggleEditing) forControlEvents:UIControlEventTouchUpInside];

    [[self view] addSubview:displayImage];
    [[self view] addSubview:photoView];
    [[self view] addSubview:maskView];
    [[self view] addSubview:btnEdit];

    [self updateMaskedImage];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

-(void)dealloc
{
    [btnEdit release];

    [super dealloc];
}

#pragma mark -
#pragma mark Update Masked Image Method
#pragma mark -

-(void)updateMaskedImage
{
    maskView.hidden = YES;

    UIImage *finalImage = 
    [self maskImage:[self captureView:self.view]
           withMask:[UIImage imageNamed:@"mask.png"]];


    maskView.hidden = NO;

    //UIImage *finalImage = [self maskImage:photoView.image withMask:[UIImage imageNamed:@"mask.png"]];

    [displayImage setImage:finalImage];
}

- (UIImage*) maskImage:(UIImage *)image withMask:(UIImage *)maskImage {

    CGImageRef maskRef = maskImage.CGImage; 

    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                        CGImageGetHeight(maskRef),
                                        CGImageGetBitsPerComponent(maskRef),
                                        CGImageGetBitsPerPixel(maskRef),
                                        CGImageGetBytesPerRow(maskRef),
                                        CGImageGetDataProvider(maskRef), NULL, false);

    CGImageRef masked = CGImageCreateWithMask([image CGImage], mask);
    return [UIImage imageWithCGImage:masked];

}

#pragma mark -
#pragma mark Touches Began
#pragma mark -

// adjusts the editing flag to make dragging and drop work
-(void)toggleEditing
{
    if(!isEditing)
    {
        isEditing = true;

        NSLog(@"editing...");

        [btnEdit setTitle:@"Stop Editing" forState:UIControlStateNormal];

        displayImage.hidden = YES;
        photoView.hidden = NO;
        maskView.hidden = NO;
    }
    else
    {
        isEditing = false;

        [self updateMaskedImage];

        NSLog(@"stopped editting");

        [btnEdit setTitle:@"Start Editing" forState:UIControlStateNormal];

        displayImage.hidden = NO;
        photoView.hidden = YES;
        maskView.hidden = YES;
    }
}

/*
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{   
    if(isEditing)
    {
        UITouch *finger = [touches anyObject];
        CGPoint currentPosition = [finger locationInView:self.view];

        //[maskView setCenter:currentPosition];
        //[photoView setCenter:currentPosition];
        if([touches count] == 1)
        {
            [photoView setCenter:currentPosition];
        }
        else if([touches count] == 2)
        {

        }
    }
}
*/

-(void)handlePan:(UIPanGestureRecognizer *)recognizer
{    
    CGPoint translation = [recognizer translationInView:self.view];
    recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, 
                                         recognizer.view.center.y + translation.y);
    [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}

-(void)handlePinch:(UIPinchGestureRecognizer *)recognizer
{    
    recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
    recognizer.scale = 1;
}

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

#pragma mark -
#pragma mark Capture Screen Function
#pragma mark -

- (UIImage*)captureView:(UIView *)yourView 
{
    UIGraphicsBeginImageContextWithOptions(yourView.bounds.size, yourView.opaque, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [yourView.layer renderInContext:context];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

#pragma mark -

@end

So I created the custom gesture recognizer in light of no one giving me a better solution that achieved the desired results. 所以我创建了自定义手势识别器,因为没有人给我一个更好的解决方案,达到了预期的效果。 Below are the key code fragments that allow the custom recognizer to indicate where the view should reposition and what its new scale should be with the centroid as the center of the pan and zoom effects so that the pixels under the fingers remain under the fingers at all time, unless the fingers appear to rotate, which is not supported and I can't do anything to stop them from such a gesture. 下面是关键代码片段,允许自定义识别器指示视图应重新定位的位置以及新的比例应该是什么,以质心作为平移和缩放效果的中心,以便手指下方的像素保持在手指下方时间,除非手指看起来旋转,这是不受支持的,我无法做任何事情来阻止他们这样的手势。 This gesture recognizer pans and zooms simultaneously with two fingers. 该手势识别器用两个手指同时平移和缩放。 I need to add support later for one finger panning, even when one of two fingers is lifted up. 我需要稍后为一个手指平移添加支持,即使两个手指中的一个被抬起。

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    // We can only process if we have two fingers down...
    if ( FirstFinger == nil || SecondFinger == nil )
        return;

    // We do not attempt to determine if the first finger, second finger, or
    // both fingers are the reason for this method call. For this reason, we
    // do not know if either is stale or updated, and thus we cannot rely
    // upon the UITouch's previousLocationInView method. Therefore, we need to
    // cache the latest UITouch's locationInView information each pass.

    // Break down the previous finger coordinates...
    float A0x = PreviousFirstFinger.x;
    float A0y = PreviousFirstFinger.y;
    float A1x = PreviousSecondFinger.x;
    float A1y = PreviousSecondFinger.y;
    // Update our cache with the current fingers for next pass through here...
    PreviousFirstFinger = [FirstFinger locationInView:nil];
    PreviousSecondFinger = [SecondFinger locationInView:nil];
    // Break down the current finger coordinates...
    float B0x = PreviousFirstFinger.x;
    float B0y = PreviousFirstFinger.y;
    float B1x = PreviousSecondFinger.x;
    float B1y = PreviousSecondFinger.y;


    // Calculate the zoom resulting from the two fingers moving toward or away from each other...
    float OldScale = Scale;
    Scale *= sqrt((B0x-B1x)*(B0x-B1x) + (B0y-B1y)*(B0y-B1y))/sqrt((A0x-A1x)*(A0x-A1x) + (A0y-A1y)*(A0y-A1y));

    // Calculate the old and new centroids so that we can compare the centroid's movement...
    CGPoint OldCentroid = { (A0x + A1x)/2, (A0y + A1y)/2 };
    CGPoint NewCentroid = { (B0x + B1x)/2, (B0y + B1y)/2 };    

    // Calculate the pan values to apply to the view so that the combination of zoom and pan
    // appear to apply to the centroid rather than the center of the view...
    Center.x = NewCentroid.x + (Scale/OldScale)*(self.view.center.x - OldCentroid.x);
    Center.y = NewCentroid.y + (Scale/OldScale)*(self.view.center.y - OldCentroid.y);
}

The view controller handles the events by assigning the new scale and center to the view in question. 视图控制器通过将新比例和中心分配给相关视图来处理事件。 I noticed that other gesture recognizers tend to allow the controller to do some of the math, but I tried to do all the math in the recognizer. 我注意到其他手势识别器倾向于允许控制器进行一些数学运算,但我试图在识别器中进行所有数学运算。

-(void)handlePixelTrack:(PixelTrackGestureRecognizer*)sender
{
    sender.view.center= sender.Center;
    sender.view.transform = CGAffineTransformMakeScale(sender.Scale, sender.Scale);
}

The easier solution is to put your view inside a scroll view. 更简单的解决方案是将视图放在滚动视图中。 Then you get pinch and pan for free. 然后你可以免费获得捏和平底锅。 Otherwise, you can set both your pan and pinch gesture delegates to self and return YES for shouldRecognizeSimultaneously. 否则,您可以将pan和pinch手势委托设置为self,并为shouldRecognizeSimultaneously返回YES。 As for zooming into the center of the user's fingers, I never did solve that one correctly but it involves manipulating the anchorPoint of your view's layer before changing its scale (I think). 至于缩放到用户手指的中心,我从来没有正确地解决过这个问题,但它涉及在改变其比例之前操纵视图层的anchorPoint (我认为)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM