UIGestureRecognizer 对象会截取本应由视图处理的触摸事件。当某个UIGestureRecognizer对象识别出特定的手势后,就会向指定的对象发送指定的消息。iOS SDK默认提供若干中UIGestureRecoginezer对象。本章我们将继续更新 JXTouchTracker ,借助由iOS SDK提供的三种 UIGestureRecogniezer对象,用户可以选择、移动、删除线条。

  • UIGestureRecognizer子类

  在为应用添加手势识别功能时,需要针对特定的手势创建响应的UIGestureRecognizer子类对象,而不是直接使用UIGestureRecognizer对象。iOS SDK提供了多种能够处理不同手势的UIGestureRecognizer子类。

  使用UIGestureRecognizer子类对象时,除了要设置目标动作对,还要将该子类对象“附着”在某个视图上。当该子类对象根据当前附着的视图所发生的触摸事件识别出相应的手势时,就会向指定的目标对象发送指定的动作消息。由UIGestureRecognizer对象发出的动作消息都会遵守以下规范:

- (void)action:(UIGestureRecognizer *)gestureRecognizer

  UIGestureRecognizer对象在识别手势时,会截取本应由其附着的视图自行处理的触摸事件。因此,附着了 UIGestureRecognizer 对象的视图可能不会受到常规的 UIResponder 消息,例如,不会收到: - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 消息。

  • 用UITapGestureRecognizer对象识别点击手势

  下面为我们应用添加一个功能,当用户双击屏幕时,会清除屏幕上的所有线条。

#import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView ()
/** 保存当前正在绘制线条 */
@property (nonatomic,strong) JXLine * currentLine;
/** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress; @end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired =
;
[self addGestureRecognizer:doubleTapRecoginzer];

}
return self;
} // 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
}
- (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end

  构建并运行,同时我们可以检测触摸事件发生的顺序:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

- (void)doubleTap:(UIGestureRecognizer *)tap 

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

  由于 UIGestureRecognizer 对象会通过截取触摸事件来识别手势,因此在UIGestureRecognizer 对象识别出手势之前,UIView 会收到所有 UIResponder 消息。对于 UITapGestureRecognizer来说,在识别出点击手势之前,UIView 会收到  - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 消息:在识别出点击手势之后,UITapGestureRecognizer 会自行处理相关触摸事件,由这些触摸事件所引发的 UIResponder 消息将不会再发送给 UIView 。直到 UITapGestureRecognizer 检测出点击手势已经结束,UIView 才会重新收到 UIResponder 消息( - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event )

  为了在识别出点击手势之前避免向 UIView 发送 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 消息。我们需要在代码中做如下修改:

#import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView ()
/** 保存当前正在绘制线条 */
@property (nonatomic,strong) JXLine * currentLine;
/** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress; @end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan =
YES;
[self addGestureRecognizer:doubleTapRecoginzer];
}
return self;
} // 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} - (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end
  • 同时添加多种触摸手势

  接下来我们为应用中添加单击手势,让用户可以选择屏幕上的线条

#import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView ()
/** 保存当前正在绘制线条 */
@property (nonatomic,strong) JXLine * currentLine;
/** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress; @end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecoginzer]; // 添加单机事件
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan =
YES;
[self addGestureRecognizer:tapRecognizer];
}
return self;
}
// 添加单机事件
- (void)tap:(UIGestureRecognizer *)tap {
NSLog(@"%s",__func__);
}
// 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} - (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end

  构建并运行。可以发现,点击一次可以正确识别出单击手势,控制台会输出单击方法信息;但是如果我们双击,应用将无法识别出正确的单击手势,单击双击手势方法都会执行。

  如果需要为视图添加多种手势,就需要考虑这些手势之间的关系。双击手势包含两次单击,为了避免 UITapGestureRecognizer 将双击时间分拆为两个单击事件,可以设置UITapGestureRecognizer 在单击后暂时不进行识别,知道确定不是双击手势后再识别为单击手势。

#import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView ()
/** 保存当前正在绘制线条 */
@property (nonatomic,strong) JXLine * currentLine;
/** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress; @end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecoginzer]; // 添加单机事件
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
// 用来防止将双击事件拆分为单击
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecoginzer];
[self addGestureRecognizer:tapRecognizer]; }
return self;
}
// 添加单机事件
- (void)tap:(UIGestureRecognizer *)tap {
NSLog(@"%s",__func__);
}
// 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} - (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end

  构建并运行应用,单击屏幕,UITapGestureRecognizer 会稍作停顿,确定是单击手势之后再执行 tap: 方法。而双击之后就不会执行这个方法了。

  现在为项目 添加单击选择线条功能。

#import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView () /** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress; /** 保存选中的线条 */
@property (nonatomic,weak) JXLine * selectedLine;
@end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecoginzer]; // 添加单机事件
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
// 用来防止将双击事件拆分为单击
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecoginzer];
[self addGestureRecognizer:tapRecognizer]; }
return self;
}
// 添加单机事件
- (void)tap:(UIGestureRecognizer *)tap {
NSLog(@"%s",__func__); CGPoint point = [tap locationInView:self];
self.selectedLine =
[self lineAtPoint:point]; [self setNeedsDisplay];
}
// 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} // 画线
- (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} if (self.selectedLine) {
[[UIColor greenColor] set
];
[self strokeLine:self.selectedLine];
}
} // 根据传入的位置找出距离最近的那个对象
- (JXLine *)lineAtPoint:(CGPoint)p { // 找出离p最近的JXLine对象
for (JXLine * line in self.finishedLines) {
CGPoint start = line.begin;
CGPoint end = line.end; // 检查线条的若干个点
for (float t = 0.0; t <= 1.0; t += 0.05) {
float x = start.x + t * (end.x - start.x);
float y = start.y + t * (end.y - start.y); // 如果线条的某个点和p的距离在20点以内,就返回响应的JXLIne对象
if (hypot(x-p.x, y-p.y) < 20.0) {
return line;
}
}
} // 如果没有找到符合条件的线条,就返回nil
return nil;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end

  构建并运行:

  • UIMenuController

  下面我们要为应用添加另一个功能:当用户选中某根线条时,我们要在用户手指按下的位置显示一个菜单。这个菜单要为用户提供一个删除选项。iOS提供了一个名为 UIMenuController 的类,可以用来显示这类菜单。

  每个iOS应用只有一个 UIMenuController 对象。当应用要显示该对象时,要现为他设置一组 UIMenuItem 对象,然后设置显示位置,最后将其设置为可见。

#import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView () /** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress; /** 保存选中的线条 */
@property (nonatomic,weak) JXLine * selectedLine; @end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecoginzer]; // 添加单机事件
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
// 用来防止将双击事件拆分为单击
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecoginzer];
[self addGestureRecognizer:tapRecognizer]; }
return self;
}
// 添加单机事件
- (void)tap:(UIGestureRecognizer *)tap { CGPoint point = [tap locationInView:self];
self.selectedLine = [self lineAtPoint:point]; // 当有选中线条时
if (self.selectedLine) { // 是视图成为 UIMenuItem 动作消息的目标
[self becomeFirstResponder]; // 获取 UIMenuController 对象
UIMenuController * menu = [UIMenuController sharedMenuController]; // 创建一个新的标题为“Delete”的UIMenuItem对象
UIMenuItem * deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
menu.menuItems = @[deleteItem]; // 先为 UIMenuController 对象设置显示区域,然后将其设置为可见
[menu setTargetRect:CGRectMake(point.x, point.y, , ) inView:self];
[menu setMenuVisible:YES animated:YES];
} else {
// 如果没有选中的线条,就隐藏 UIMenuController 对象
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
}

[self setNeedsDisplay];
}
// 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} // 画线
- (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} if (self.selectedLine) {
[[UIColor greenColor] set];
[self strokeLine:self.selectedLine];
} } // 根据传入的位置找出距离最近的那个对象
- (JXLine *)lineAtPoint:(CGPoint)p { // 找出离p最近的JXLine对象
for (JXLine * line in self.finishedLines) {
CGPoint start = line.begin;
CGPoint end = line.end; // 检查线条的若干个点
for (float t = 0.0; t <= 1.0; t += 0.05) {
float x = start.x + t * (end.x - start.x);
float y = start.y + t * (end.y - start.y); // 如果线条的某个点和p的距离在20点以内,就返回响应的JXLIne对象
if (hypot(x-p.x, y-p.y) < 20.0) {
return line;
}
}
} // 如果没有找到符合条件的线条,就返回nil
return nil;
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end

  要显示 UIMenuController 对象,还要满足一个条件:显示UIMenuController对象的UIView对象必须是当前UIWindow对象的第一响应对象。这也是为什么在 tap: 方法中起始部分会向JXDrawView 发送  becomeFirstResponder 消息。如果要将某个自定义的UIView子类对象设置为第一响应对象,就必须覆盖该对象的  canBecomeFirstResponder 方法:

// 将某个自定义的UIView子类对象设置为第一响应对象,就必须覆盖此类方法
- (BOOL)canBecomeFirstResponder {
return YES;
}

  实现删除选中线条方法:

#import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView () /** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress; /** 保存选中的线条 */
@property (nonatomic,weak) JXLine * selectedLine; @end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecoginzer]; // 添加单机事件
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
// 用来防止将双击事件拆分为单击
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecoginzer];
[self addGestureRecognizer:tapRecognizer]; }
return self;
}
// 添加单机事件
- (void)tap:(UIGestureRecognizer *)tap { CGPoint point = [tap locationInView:self];
self.selectedLine = [self lineAtPoint:point]; // 当有选中线条时
if (self.selectedLine) { // 是视图成为 UIMenuItem 动作消息的目标
[self becomeFirstResponder]; // 获取 UIMenuController 对象
UIMenuController * menu = [UIMenuController sharedMenuController]; // 创建一个新的标题为“Delete”的UIMenuItem对象
UIMenuItem * deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
menu.menuItems = @[deleteItem]; // 先为 UIMenuController 对象设置显示区域,然后将其设置为可见
[menu setTargetRect:CGRectMake(point.x, point.y, , ) inView:self];
[menu setMenuVisible:YES animated:YES];
} else {
// 如果没有选中的线条,就隐藏 UIMenuController 对象
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
}
[self setNeedsDisplay];
} - (void)deleteLine:(id)sender {
// 从已经完成的小太中删除选中的线条
[self.finishedLines removeObject:self.selectedLine]; // 重画整个视图
[self setNeedsDisplay];
}
// 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} // 画线
- (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} if (self.selectedLine) {
[[UIColor greenColor] set];
[self strokeLine:self.selectedLine];
} } // 根据传入的位置找出距离最近的那个对象
- (JXLine *)lineAtPoint:(CGPoint)p { // 找出离p最近的JXLine对象
for (JXLine * line in self.finishedLines) {
CGPoint start = line.begin;
CGPoint end = line.end; // 检查线条的若干个点
for (float t = 0.0; t <= 1.0; t += 0.05) {
float x = start.x + t * (end.x - start.x);
float y = start.y + t * (end.y - start.y); // 如果线条的某个点和p的距离在20点以内,就返回响应的JXLIne对象
if (hypot(x-p.x, y-p.y) < 20.0) {
return line;
}
}
} // 如果没有找到符合条件的线条,就返回nil
return nil;
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end
  • UIPanGestureRecoginzer 以及同事识别多个手势

  当用户按住某根线条不放时,应用应该允许通过移动手指来拖拽选中的线条。这类手势成为拖动(pan)。可以用 UIPanGestureRecoginzer 对象来识别。

  通常情况下,UIGestureRecognizer 对象不会将其处理过的触摸事件再交给其他对象来处理。一旦某个 UIGestureRecognizer 子类对象识别出了响应的手势,就会吃掉所有相关的触摸事件,导致其他 UIGestureRecognizer 对象没有机会再处理这些触摸事件。对本应用来说,这种特性会导致 JXDrawView 对象无法处理拖动手势,这是因为整个拖动手势都是在长按手势中发生的。要解决这个问题,需要让 UILongPressGestureRecognizer 对象和 UIPanGestureRecoginzer 对象能够同时识别手势。

  

//
// JXDrawView.m
// JXTouchTracker
//
// Created by 王加祥 on 16/10/8.
// Copyright © 2016年 王加祥. All rights reserved.
// #import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView ()<UIGestureRecognizerDelegate> /** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress;
/** 保存选中的线条 */
@property (nonatomic,weak) JXLine * selectedLine;
/** 移动手势 */
@property (nonatomic,strong) UIPanGestureRecognizer * moveRecognizer;
@end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecoginzer]; // 添加单机事件
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
// 用来防止将双击事件拆分为单击
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecoginzer];
[self addGestureRecognizer:tapRecognizer]; // 添加长按手势
UILongPressGestureRecognizer * pressRecoginzer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[self addGestureRecognizer:pressRecoginzer]; // 移动手势
self.moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];
self.moveRecognizer.delegate = self;
self.moveRecognizer.cancelsTouchesInView =
NO;
[self addGestureRecognizer:self.moveRecognizer];

}
return self;
} #pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer == self.moveRecognizer) {
return YES;
}
return NO;
} - (void)moveLine:(UIPanGestureRecognizer *)panGesture {
// 如果没有选中的线条就直接返回
if (!self.selectedLine) {
return;
} // 如果 UIPanGestureRecoginzer 对象处于 “变化后”的状态
if (panGesture.state == UIGestureRecognizerStateChanged) {
// 获取手指的拖移距离
CGPoint translation = [panGesture translationInView:self]; // 将拖动距离加至选中的线条的起点和终点
CGPoint begin = self.selectedLine.begin;
CGPoint end = self.selectedLine.end;
begin.x += translation.x;
begin.y += translation.y;
end.x += translation.x;
end.y += translation.y; // 为选中的线条设置新的起点和终点
self.selectedLine.begin = begin;
self.selectedLine.end = end; // 重画视图
[self setNeedsDisplay];
}
}
// 添加长按手势
- (void)longPress:(UIGestureRecognizer *)press {
if (press.state == UIGestureRecognizerStateBegan) { CGPoint point = [press locationInView:self];
self.selectedLine = [self lineAtPoint:point]; if (self.selectedLine) {
[self.linesInProgress removeAllObjects];
}
} else if (press.state == UIGestureRecognizerStateEnded) {
self.selectedLine = nil;
}
[self setNeedsDisplay];
}
// 添加单机事件
- (void)tap:(UIGestureRecognizer *)tap { CGPoint point = [tap locationInView:self];
self.selectedLine = [self lineAtPoint:point]; // 当有选中线条时
if (self.selectedLine) { // 是视图成为 UIMenuItem 动作消息的目标
[self becomeFirstResponder]; // 获取 UIMenuController 对象
UIMenuController * menu = [UIMenuController sharedMenuController]; // 创建一个新的标题为“Delete”的UIMenuItem对象
UIMenuItem * deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
menu.menuItems = @[deleteItem]; // 先为 UIMenuController 对象设置显示区域,然后将其设置为可见
[menu setTargetRect:CGRectMake(point.x, point.y, , ) inView:self];
[menu setMenuVisible:YES animated:YES];
} else {
// 如果没有选中的线条,就隐藏 UIMenuController 对象
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
}
[self setNeedsDisplay];
} - (void)deleteLine:(id)sender {
// 从已经完成的小太中删除选中的线条
[self.finishedLines removeObject:self.selectedLine]; // 重画整个视图
[self setNeedsDisplay];
} // 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} // 将某个自定义的UIView子类对象设置为第一响应对象,就必须覆盖此类方法
- (BOOL)canBecomeFirstResponder {
return YES;
}
// 画线
- (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} if (self.selectedLine) {
[[UIColor greenColor] set];
[self strokeLine:self.selectedLine];
} } // 根据传入的位置找出距离最近的那个对象
- (JXLine *)lineAtPoint:(CGPoint)p { // 找出离p最近的JXLine对象
for (JXLine * line in self.finishedLines) {
CGPoint start = line.begin;
CGPoint end = line.end; // 检查线条的若干个点
for (float t = 0.0; t <= 1.0; t += 0.05) {
float x = start.x + t * (end.x - start.x);
float y = start.y + t * (end.y - start.y); // 如果线条的某个点和p的距离在20点以内,就返回响应的JXLIne对象
if (hypot(x-p.x, y-p.y) < 20.0) {
return line;
}
}
} // 如果没有找到符合条件的线条,就返回nil
return nil;
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end

  UIGestureRecognizerDelegate 协议声明了许多方法,目前 JXDrawView 只需要用到: - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 当某个 UIGestureRecognizer 子类对象识别出特定的手势后,如果发现其他的 UIGestureRecognizer 子类对象也识别出了特定的手势,就会向其委托对象发送上述消息。如果相应的方法返回 YES ,那么当前的 UIGestureRecognizer 子类对象就会和其他 UIGestureRecognizer 子类对象共享 UITouch 对象。

  当我们设置好移动消息时,如果我们按住某根线条不放时,UIPanGestureRecoginzer 对象也能收到相关的 UITouch 对象,从而可以跟踪用户的手指移动。当用户的手指开始移动时。UIPanGestureRecoginzer 对象的状态也会切换至 “开始”。如果 UILongPressGestureRecognizer 对象和 UIPanGestureRecoginzer 对象不能同时识别手势,那么当用户的手指开始在屏幕上移动时,UILongPressGestureRecognizer 对象的状态还是会切换至 “开始”,但是 UIPanGestureRecoginzer 对象的状态不会发生变化,也不会向其目标对象发送动作消息。

  构建上述代码,运行。当我们开始拖动的时候会发现当前选中的线条位置并不能和手指的位置保持一致。这是因为  moveLine: 会持续累加当前选中的线条的气起点和终点。

//
// JXDrawView.m
// JXTouchTracker
//
// Created by 王加祥 on 16/10/8.
// Copyright © 2016年 王加祥. All rights reserved.
// #import "JXDrawView.h"
#import "JXLine.h" @interface JXDrawView ()<UIGestureRecognizerDelegate> /** 保存已经绘制完成的线条 */
@property (nonatomic,strong) NSMutableArray * finishedLines;
/** 保存正在绘制的多条直线 */
@property (nonatomic,strong) NSMutableDictionary * linesInProgress;
/** 保存选中的线条 */
@property (nonatomic,weak) JXLine * selectedLine;
/** 移动手势 */
@property (nonatomic,strong) UIPanGestureRecognizer * moveRecognizer; @end @implementation JXDrawView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { self.linesInProgress = [NSMutableDictionary dictionary];
self.finishedLines = [NSMutableArray array];
self.backgroundColor = [UIColor grayColor]; // 支持多点触摸
self.multipleTouchEnabled = YES; // 添加点击事件
UITapGestureRecognizer * doubleTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecoginzer.numberOfTapsRequired = ;
// 为了避免在识别出点击手势之前出发touches手势
doubleTapRecoginzer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecoginzer]; // 添加单机事件
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
// 用来防止将双击事件拆分为单击
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecoginzer];
[self addGestureRecognizer:tapRecognizer]; // 添加长按手势
UILongPressGestureRecognizer * pressRecoginzer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[self addGestureRecognizer:pressRecoginzer]; // 移动手势
self.moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];
self.moveRecognizer.delegate = self;
self.moveRecognizer.cancelsTouchesInView = NO;
[self addGestureRecognizer:self.moveRecognizer];
}
return self;
} #pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer == self.moveRecognizer) {
return YES;
}
return NO;
} - (void)moveLine:(UIPanGestureRecognizer *)panGesture {
// 如果没有选中的线条就直接返回
if (!self.selectedLine) {
return;
} // 如果 UIPanGestureRecoginzer 对象处于 “变化后”的状态
if (panGesture.state == UIGestureRecognizerStateChanged) {
// 获取手指的拖移距离
CGPoint translation = [panGesture translationInView:self]; // 将拖动距离加至选中的线条的起点和终点
CGPoint begin = self.selectedLine.begin;
CGPoint end = self.selectedLine.end;
begin.x += translation.x;
begin.y += translation.y;
end.x += translation.x;
end.y += translation.y; // 为选中的线条设置新的起点和终点
self.selectedLine.begin = begin;
self.selectedLine.end = end; // 重画视图
[self setNeedsDisplay]; // 每次移动过后将手指的当前位置设置为手指的起始位置
[panGesture setTranslation:CGPointZero inView:self];
}
} // 添加长按手势
- (void)longPress:(UIGestureRecognizer *)press {
if (press.state == UIGestureRecognizerStateBegan) { CGPoint point = [press locationInView:self];
self.selectedLine = [self lineAtPoint:point]; if (self.selectedLine) {
[self.linesInProgress removeAllObjects];
}
} else if (press.state == UIGestureRecognizerStateEnded) {
self.selectedLine = nil;
}
[self setNeedsDisplay];
}
// 添加单机事件
- (void)tap:(UIGestureRecognizer *)tap { CGPoint point = [tap locationInView:self];
self.selectedLine = [self lineAtPoint:point]; // 当有选中线条时
if (self.selectedLine) { // 是视图成为 UIMenuItem 动作消息的目标
[self becomeFirstResponder]; // 获取 UIMenuController 对象
UIMenuController * menu = [UIMenuController sharedMenuController]; // 创建一个新的标题为“Delete”的UIMenuItem对象
UIMenuItem * deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
menu.menuItems = @[deleteItem]; // 先为 UIMenuController 对象设置显示区域,然后将其设置为可见
[menu setTargetRect:CGRectMake(point.x, point.y, , ) inView:self];
[menu setMenuVisible:YES animated:YES];
} else {
// 如果没有选中的线条,就隐藏 UIMenuController 对象
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
}
[self setNeedsDisplay];
} - (void)deleteLine:(id)sender {
// 从已经完成的小太中删除选中的线条
[self.finishedLines removeObject:self.selectedLine]; // 重画整个视图
[self setNeedsDisplay];
} // 添加手势
- (void)doubleTap:(UIGestureRecognizer *)tap {
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
} // 将某个自定义的UIView子类对象设置为第一响应对象,就必须覆盖此类方法
- (BOOL)canBecomeFirstResponder {
return YES;
}
// 画线
- (void)strokeLine:(JXLine *)line {
UIBezierPath * bp = [UIBezierPath bezierPath];
bp.lineWidth = ;
bp.lineCapStyle = kCGLineCapRound; [bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
} - (void)drawRect:(CGRect)rect {
// 用黑色表示已经绘制完成的线条
[[UIColor blackColor] set];
for (JXLine * line in self.finishedLines) {
[self strokeLine:line];
} // 用红色绘制正在画的线条
[[UIColor redColor] set];
for (NSValue * key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
} if (self.selectedLine) {
[[UIColor greenColor] set];
[self strokeLine:self.selectedLine];
} } // 根据传入的位置找出距离最近的那个对象
- (JXLine *)lineAtPoint:(CGPoint)p { // 找出离p最近的JXLine对象
for (JXLine * line in self.finishedLines) {
CGPoint start = line.begin;
CGPoint end = line.end; // 检查线条的若干个点
for (float t = 0.0; t <= 1.0; t += 0.05) {
float x = start.x + t * (end.x - start.x);
float y = start.y + t * (end.y - start.y); // 如果线条的某个点和p的距离在20点以内,就返回响应的JXLIne对象
if (hypot(x-p.x, y-p.y) < 20.0) {
return line;
}
}
} // 如果没有找到符合条件的线条,就返回nil
return nil;
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
CGPoint location = [t locationInView:self]; JXLine * line = [[JXLine alloc] init];
line.begin = location;
line.end = location; NSValue * key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
} [self setNeedsDisplay];
} - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch * t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key];
line.end = [t locationInView:self];
} [self setNeedsDisplay];
} - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
JXLine * line = self.linesInProgress[key]; [self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
} [self setNeedsDisplay];
} - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {
NSValue * key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
} @end

iOS UIGestureRecognizer与UIMenuController(内容根据iOS编程)的更多相关文章

  1. iOS 视图控制器 (内容根据iOS编程编写)

    视图控制器是  UIViewController 类或其子类对象.每个视图控制器都负责管理一个视图层次结构,包括创建视图层级结构中的视图并处理相关用户事件,以及将整个视图层次结构添加到应用窗口. 创建 ...

  2. iOS Programming UIGestureRecognizer and UIMenuController

    iOS  Programming  UIGestureRecognizer and UIMenuController A UIGestureRecognizer intercepts touches ...

  3. 为iOS 7而开发 并支持iOS 6

    除了写这本“Developing an iOS 7 Edge”书之外,我还针对iOS 7更新了app,所以我想我应该和大家分享一下我的收获.如果你正在面向iOS 7系统更新应用,同时你的应用还支持iO ...

  4. iOS 9的新的改变 iOS SDK Release Notes for iOS 9 说了些改变

    iOS 9的新的改变 iOS SDK Release Notes for iOS 9 说了些改变   看了下还算能理解!!!有兴趣可以看看哈!!!不喜勿喷!!后面的对于废除的方法什么有用感觉!!!   ...

  5. iOS 9学习系列:打通 iOS 9 的通用链接(Universal Links)

    在WWDC 2015 上, Apple 为 iOS 9 宣布了一个所谓 通用链接 的深层链接特性, 视频地址为 [无缝链接到您的 App].虽然它不是一个必须实现的功能, 但还是需要引起一些注意. 在 ...

  6. iOS Simulator功能介绍关于Xamarin IOS开发

    iOS Simulator功能介绍关于Xamarin IOS开发 iOS Simulator功能介绍 在图1.38所示的运行效果中,所见到的类似于手机的模型就是iOS Simulator.在没有iPh ...

  7. iOS 9应用开发教程之iOS 9新特性

    iOS 9应用开发教程之iOS 9新特性 iOS 9开发概述 iOS 9是目前苹果公司用于苹果手机和苹果平板电脑的最新的操作系统.该操作系统于2015年6月8号(美国时间)被发布.本章将主要讲解iOS ...

  8. 从iOS 11 UI Kit中谈谈iOS 11的新变化

    北京时间9月20日凌晨1点,iOS 11终于迎来了正式版的推送,相信各位小伙伴已经在第一时间进行了升级.iOS 11毫无疑问是一次大规模的系统更新,UI.系统内核.锁屏等多方面都进行了不同程度的改进. ...

  9. [IOS]从零开始搭建基于Xcode7的IOS开发环境和免开发者帐号真机调试运行第一个IOS程序HelloWorld

    首先这篇文章比较长,若想了解Xcode7的免开发者帐号真机调试运行IOS程序的话,直接转到第五部分. 转载请注明原文地址:http://www.cnblogs.com/litou/p/4843772. ...

随机推荐

  1. linux内核学习之三 跟踪分析内核的启动过程

    一   前期准备工作       1 搭建环境 1.1下载内核源代码并编译内核 创建目录,并进入该目录: 下载源码: 解压缩,并进入该目录:xz -d linux-3.18.6.tar.xz tar ...

  2. toB的产品经理和toc产品经理区别

    腾讯产品经理现身说法 曾经在UC做过2年to c的app,现在在腾讯做to b的产品. 做to c产品的时候,我很瞧不起做to b产品的同学,认为他们不过是做支撑的. 后来,我参与了一个to b平台级 ...

  3. ZOJ 3349 Special Subsequence 简单DP + 线段树

    同 HDU 2836 只不过改成了求最长子串. DP+线段树单点修改+区间查最值. #include <cstdio> #include <cstring> #include ...

  4. ArcGIS Engine实现LAS数据集转RASTER

    ArcGIS 10.1版本开始提供了将LAS数据集转换为栅格的新功能,最近在做LAS数据处理时通过ArcGIS Engine10.1+C#实现了LAS数据集转RASTER,既然ArcGIS DeskT ...

  5. 关于Bayes网络新解

    经典贝叶斯网络 贝叶斯分类器的分类原理是通过某对象的先验概率,利用贝叶斯公式计算出其后验概率,即该对象属于某一类的概率,选择具有最大后验概率的类作为该对象所属的类.目前研究较多的贝叶斯分类器主要有四种 ...

  6. cocos2d-x 那些常犯的错误

    Label::_label; if(_label==NULL){ //初始化_label的代码 } //指针默认值不等于NULL,需要赋初始值Label::_label=NULL; string st ...

  7. PAT 乙级 1086 就不告诉你 (15 分)

    1086 就不告诉你 (15 分) 做作业的时候,邻座的小盆友问你:“五乘以七等于多少?”你应该不失礼貌地围笑着告诉他:“五十三.”本题就要求你,对任何一对给定的正整数,倒着输出它们的乘积. 输入格式 ...

  8. pycharm设置连接github

    pycharm与guthub连起来,推送代码会方便一些 教程很多,转发一个:https://www.cnblogs.com/feixuelove1009/p/5955332.html

  9. JS原生添加删除class的方法

    之前习惯了使用jquery的addClass的方法,然后就去找了下别人写的代码. [javascript] view plain copy function hasClass(obj,cls) { r ...

  10. 在mvc视图中实现rdlc报表展示

    需求:在view视图页面中嵌入rdlc报表,rdlc的xml为动态传入的xml字符串.本项目是基于abp框架 可能出现问题: 1.rdlc报表是由asp.net的服务器控件ReportViewer来支 ...