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

我正在为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]; } } 

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

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

在这里,我用我的手支持绘制,你可以在下面看到“你好”,他们发现了一些奇怪的绘图。 我怎么能拒绝那些接触并删除它们而只是打招呼

谢谢

兰吉特

一种解决方案是将最顶部的水龙头存储在touchesBegan并仅绘制这一个。

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

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

以下是如何做到这一点的想法:

 #import  @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来完成的。 这段代码可以针对性能进行大量优化,应该将其视为一个插图而不是生产就绪代码。

使用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 

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

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

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


最简单的是:

 self.trackingTouch = [touches anyObject]; 

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


不确定为什么文档说You should never retain an UITouch object when handling an eventYou 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的替代方案A UITouch object is persistent throughout a multi-touch sequence仅存储指向触摸的指针并仅将其用于指针比较(因此在指针后面的UITouch对象上不调用任何方法)。