如何在多点触控序列中忽略某些UITouch点

时间:2013-12-31 07:40:53

标签: ios uikit drawing core-graphics multi-touch

我正在为ipad绘制应用程序,我正在为绘图提供全屏。因此,就像我们现在一样,用户可能会用手腕支撑或将手放在屏幕上。所以我的目标是让用户用手腕/手支撑自由书写。

但应用程序应该只检测手指绘图,或忽略/拒绝手腕和手触摸并删除它们

我开始研究它,我创建了一个启用了多点触控的示例项目。

以下是我的代码

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    mouseSwiped = NO;

    for (UITouch *touch in touches)
    {
        NSString *key = [NSString stringWithFormat:@"%d", (int) touch];
        lastPoint = [touch locationInView:self.view];

        [touchPaths setObject:[NSValue valueWithCGPoint:lastPoint] forKey:key];
    }

}


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{    
    mouseSwiped = YES;
    CGPoint lastPoint3;

    for (UITouch *touch in touches)
    {
        NSString *key = [NSString stringWithFormat:@"%d", (int) touch];

        lastPoint = [[touchPaths objectForKey:key] CGPointValue];


        currentPoint1 = [touch locationInView:self.view];

        NSLog(@"Y:%f",currentPoint1.y);


        UIGraphicsBeginImageContext(self.view.frame.size);
        [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush );
        CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
        CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
        CGContextBeginPath(UIGraphicsGetCurrentContext());
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint1.x, currentPoint1.y);

        CGContextStrokePath(UIGraphicsGetCurrentContext());
        self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
        [self.tempDrawImage setAlpha:opacity];
        UIGraphicsEndImageContext();

       [touchPaths setObject:[NSValue valueWithCGPoint:currentPoint1] forKey:key];
    }
}

所以这适用于任何数量的触摸,但我不明白如何在绘图时拒绝那些手掌/手部触摸,只绘制,用户用手指/手写笔绘制。

现在,如果我画画,我得到这个东西,下面是图像

在这里,我用我的手支持画了,你可以看到下面的“你好”,他们发现了一些奇怪的画。我如何拒绝这些接触并删除它们并且只绘制你好

由于

兰吉特

3 个答案:

答案 0 :(得分:3)

一种解决方案是将最顶层的点按存储在touchesBegan中,并仅绘制此点。

正如您所指出的,您不应该保留UITouch实例,因此我建议使用弱引用。

这只会吸引一下。如果你想绘制多个手指的触摸,你需要另一种过滤手的方法(例如,许多绘图应用程序具有用于告诉应用程序手的姿势的用户设置,但这当然更复杂)。< / p>

以下是关于如何操作的想法:

#import <QuartzCore/QuartzCore.h>

@interface TViewController () {
    // We store a weak reference to the current touch that is tracked
    // for drawing.
    __weak UITouch* drawingTouch;
    // This is the previous point we drawed to, or the first point the user tapped.
    CGPoint touchStartPoint;
}
@end
@interface _TDrawView : UIView {
@public
    CGLayerRef persistentLayer, tempLayer;
}
-(void)commitDrawing;
-(void)discardDrawing;
@end

@implementation TViewController

- (void) loadView
{
    self.view = [[_TDrawView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.view.opaque = YES;
    self.view.multipleTouchEnabled = YES;
    self.view.backgroundColor = [UIColor whiteColor];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // Start with what we currently have
    UITouch* topmostTouch = self->drawingTouch;
    // Find the top-most touch
    for (UITouch *touch in touches) {
        CGPoint lastPoint = [touch locationInView:self.view];
        if(!topmostTouch || [topmostTouch locationInView:self.view].y > lastPoint.y) {
            topmostTouch = touch;
            touchStartPoint = lastPoint;
        }
    }
    // A new finger became the drawing finger, discard any previous 
    // strokes since last touchesEnded
    if(self->drawingTouch != nil && self->drawingTouch != topmostTouch) {
        [(_TDrawView*)self.view discardDrawing];
    }
    self->drawingTouch = topmostTouch;
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    // Always commit the current stroke to the persistent layer if the user
    // releases a finger. This could need some tweaking for optimal user experience.
    self->drawingTouch = nil;
    [(_TDrawView*)self.view commitDrawing];
    [self.view setNeedsDisplay];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    const CGFloat red=0, green=0, blue=0, brush=1;
    for (UITouch *touch in touches) {
        // Find the touch that we track for drawing
        if(touch == self->drawingTouch) {
            CGPoint currentPoint = [touch locationInView:self.view];

            // Draw stroke first in temporary layer
            CGContextRef ctx = CGLayerGetContext(((_TDrawView*)self.view)->tempLayer);
            CGContextSetLineCap(ctx, kCGLineCapRound);
            CGContextSetLineWidth(ctx, brush );
            CGContextSetRGBStrokeColor(ctx, red, green, blue, 1.0);
            CGContextSetBlendMode(ctx,kCGBlendModeNormal);
            CGContextBeginPath(ctx);
            CGContextMoveToPoint(ctx, touchStartPoint.x, touchStartPoint.y);
            CGContextAddLineToPoint(ctx, currentPoint.x, currentPoint.y);
            CGContextStrokePath(ctx);
            // Update the points so that the next line segment is drawn from where
            // we left off
            touchStartPoint = currentPoint;
            // repaint the layer
            [self.view setNeedsDisplay];
        }
    }
}

@end

@implementation _TDrawView

- (void) finalize {
    if(persistentLayer) CGLayerRelease(persistentLayer);
    if(tempLayer) CGLayerRelease(tempLayer);
}

- (void) drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    if(!persistentLayer) persistentLayer = CGLayerCreateWithContext(ctx, self.bounds.size, nil);
    if(!tempLayer) tempLayer = CGLayerCreateWithContext(ctx, self.bounds.size, nil);

    // Draw the persistant drawing
    CGContextDrawLayerAtPoint(ctx, CGPointMake(0, 0), persistentLayer);
    // Overlay with the temporary drawing
    CGContextDrawLayerAtPoint(ctx, CGPointMake(0, 0), tempLayer);
}

- (void)commitDrawing {
    // Persist the temporary drawing
    CGContextRef ctx = CGLayerGetContext(persistentLayer);
    CGContextDrawLayerAtPoint(ctx, CGPointMake(0, 0), tempLayer);
    [self discardDrawing];
}
- (void)discardDrawing {
    // Clears the temporary layer
    CGContextRef ctx = CGLayerGetContext(tempLayer);
    CGContextClearRect(ctx, self.bounds);
    CGContextFlush(ctx);
}
@end
编辑:我添加了一个逻辑:如果检测到新的触摸,如果当前正在绘制任何具有更高y值的笔划,则会将其删除,正如我们在评论中所讨论的那样。

通过绘制两个CGLayer来完成重叠。这段代码可以针对性能进行大量优化,应该将其视为一个插图,而不是生产就绪代码。

答案 1 :(得分:2)

使用UIPanGestureRecognizer来处理触摸,并将其maximumNumberOfTouches属性设置为1,这样它一次只能识别一次触摸。

手势识别器会处理忽略只是轻击的东西,因为它专门用于识别平底锅。此外,通过将最大触摸次数设置为1,一旦开始写入,其他任何触摸都不会对其产生任何影响,它将自动继续跟踪第一次触摸。

编辑:

这是一个简单的示例,从基本的单一视图应用程序模板开始,并使用以下内容替换#import语句下的所有内容:

@interface ViewController ()

@property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;

@property (nonatomic, strong) UIImageView *imageView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.imageView];

    self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(performPanGesture:)];
    self.panGestureRecognizer.maximumNumberOfTouches = 1;
    [self.view addGestureRecognizer:self.panGestureRecognizer];
}

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    self.imageView.frame = self.view.bounds;
}

- (void)performPanGesture:(UIPanGestureRecognizer *)panGesture
{
    if (panGesture == self.panGestureRecognizer) {
        CGPoint touchLocation = [panGesture locationInView:self.view];
//        NSLog(@"%f, %f", touchLocation.x, touchLocation.y);

        UIGraphicsBeginImageContextWithOptions(self.view.frame.size, YES, 0.0);

        CGContextRef context = UIGraphicsGetCurrentContext();

        [self.view.layer renderInContext:context];

        CGContextAddEllipseInRect(context, CGRectMake(touchLocation.x - 1, touchLocation.y - 1, 3, 3));

        CGContextDrawPath(context, kCGPathFill);

        UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();

        self.imageView.image = outputImage;
    }
}

@end

这会在接收到触摸的屏幕上绘制小圆圈。您还可以修改它以跟踪最后一个触摸点并在点之间绘制线条,这样您就可以获得连续的东西。您还可以看到用第二根手指触摸不会开始在新点上制作点,因为它只处理一次触摸,而第二次触摸则被忽略。

答案 2 :(得分:1)

当触摸开始时,从集合中触摸并保持对它的引用。你如何决定哪一个取决于你 - 希望只有一个,或者你可以检查每个触摸的位置并选择屏幕上的“最高”。

当触摸移动时,检查您存储的触摸是否仍然有效(包含在touches中),如果是,则仅使用它(您可以忽略所有其他触摸)。


最重要的是:

self.trackingTouch = [touches anyObject];

但是还有其他(更好)的方法来选择存储哪种触摸。


当他们也说You should never retain an UITouch object when handling an event时,不确定文档为什么会说A UITouch object is persistent throughout a multi-touch sequence

我之前没有看到存储触摸的问题,但这并不意味着它将来不会引起问题(某种程度上)。基于A UITouch object is persistent throughout a multi-touch sequence的替代方法是仅存储指向触摸的指针并仅将其用于指针比较(因此在指针后面的UITouch对象上不调用任何方法)。