UIGestureRecognizer

  继续上篇UITouch - BNR。该篇将实现线条选择、移动和删除操作。

  UIGestureRecognizer有一系列子类,每一个子类都用于识别特定的手势。当识别出一个手势时,手势识别器会拦截视图的触摸事件。

  使用UITapGestureRecognizer类,实现当用户连续点击屏幕两次时,全部线条都被清空。

修改BNRDrawView类的initWithFrame:方法如下:

 1 - (instancetype)initWithFrame:(CGRect)r {
 2     self = [super initWithFrame:r];
 3     if (self) {
 4         self.linesInProgress = [[NSMutableDictionary alloc] init];
 5         self.finishedLines = [[NSMutableArray alloc] init];
 6         self.backgroundColor = [UIColor grayColor];
 7         self.multipleTouchEnabled = YES;
 8         
 9         UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc]
10                                                        initWithTarget:self
11                                                        action:@selector(doubleTap:)];
12         doubleTapRecognizer.numberOfTapsRequired = 2;
13         doubleTapRecognizer.delaysTouchesBegan = YES;  //使第一次点击不产生红点14         [self addGestureRecognizer:doubleTapRecognizer];
15     }
16     return self;
17 }

实现连续两次点击的doubleTap:响应事件,如下:

1 - (void)doubleTap:(UIGestureRecognizer *)gr {
2     NSLog(@"Recognized Double Tap");
3     [self.linesInProgress removeAllObjects];
4     [self.finishedLines removeAllObjects];
5     [self setNeedsDisplay];
6 }

添加手势识别,允许用户删除一条选定的线条。修改BNRDrawView类的initWithFrame:方法如下:

 1 - (instancetype)initWithFrame:(CGRect)r {
 2     self = [super initWithFrame:r];
 3     if (self) {
 4         self.linesInProgress = [[NSMutableDictionary alloc] init];
 5         self.finishedLines = [[NSMutableArray alloc] init];
 6         self.backgroundColor = [UIColor grayColor];
 7         self.multipleTouchEnabled = YES;
 8         
 9         UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc]
10                                                        initWithTarget:self
11                                                        action:@selector(doubleTap:)];
12         doubleTapRecognizer.numberOfTapsRequired = 2;
13         doubleTapRecognizer.delaysTouchesBegan = YES;
14         [self addGestureRecognizer:doubleTapRecognizer];
15         
16         UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
17                                                  initWithTarget:self
18                                                  action:@selector(tap:)];
19         tapRecognizer.delaysTouchesBegan = YES;
20         [tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer]; //区别点击与双击手势
21         [self addGestureRecognizer:tapRecognizer];
22     }
23     return self;
24 }

实现单击的tap:响应事件,如下:

1 - (void)tap:(UIGestureRecognizer *)gr {
2     NSLog(@"Recognized Tap");
3 }

在BNRDrawView.m文件的类扩展中声明selectedLine属性,用来表示被用户选中的线条,如下:

@property (nonatomic, weak) BNRLine *selectedLine;

  其中,finishedLines数组拥有对selectedLine的强引用。当selectedLine从finishedLines中被删除时,将变为nil。

  修改BNRDrawView类中drawRect:方法,使selectedLine显示绿色,如下:

 1 - (void)drawRect:(CGRect)rect {
 2     [[UIColor blackColor] set];
 3     for (BNRLine *line in self.finishedLines) {
 4         [self strokeLike:line];
 5     }
 6     [[UIColor redColor] set];
 7     for (NSValue *key in self.linesInProgress) {
 8         [self strokeLike:self.linesInProgress[key]];
 9     }
10     if(self.selectedLine) {
11         [[UIColor greenColor] set];
12         [self strokeLike:self.selectedLine];
13     }
14 }

获取接近点击处的线条:

 1 - (BNRLine *)lineAtPoint:(CGPoint)p {
 2     for(BNRLine *l in self.finishedLines) {
 3         CGPoint start = l.begin;
 4         CGPoint end = l.end;
 5         for (float t = 0.0; t <= 1.0; t += 0.05) {
 6             float x = start.x + t * (end.x - start.x);
 7             float y = start.y + t * (end.y - start.y);
 8             if (hypot(x - p.x, y - p.y) < 20.0) {
 9                 return l;
10             }
11         }
12     }
13     return nil;
14 }

最后修改点击手势响应事件tap:方法如下:

1 - (void)tap:(UIGestureRecognizer *)gr {
2     NSLog(@"Recognized Tap");
3     
4     CGPoint point = [gr locationInView:self];
5     self.selectedLine = [self lineAtPoint:point];
6     [self setNeedsDisplay];
7 }

  接下来实现,当用户已经选中一条线条之后,将显示一个菜单提供对该线条的删除操作。

修改tap:方法如下:

 1 - (void)tap:(UIGestureRecognizer *)gr {
 2     NSLog(@"Recognized Tap");
 3     CGPoint point = [gr locationInView:self];
 4     self.selectedLine = [self lineAtPoint:point];
 5     
 6     if(self.selectedLine) {
 7         //使该视图成为menu item响应方法的目标
 8         [self becomeFirstResponder];
 9         UIMenuController *menu = [UIMenuController sharedMenuController];
10         //创建一个新的删除UIMenuItem
11         UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
12         menu.menuItems = @[deleteItem];
13         //设置menu的显示位置
14         [menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
15         [menu setMenuVisible:YES animated:YES];
16     } else {
17         [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
18     }
19     
20     [self setNeedsDisplay];
21 }

  当自定义的视图类需要成为第一响应者时,必须重载canBecomeFirstResponder方法,在BNRDrawView.m中添加如下方法:

1 - (BOOL)canBecomeFirstResponder {
2     return YES;
3 }

  如果menu items的响应方法没有实现,该menu就不会显示。实现deleteLine:方法如下:

1 - (void)deleteLine:(id)sender {
2     [self.finishedLines removeObject:self.selectedLine];
3     [self setNeedsDisplay];
4 }

运行程序,效果如下:


  接下来,实现如下功能,当用户长按住一条线时,该线将被选中,用户能够用手指拖动该线条。修改initWithFrame:方法如下:

 1 - (instancetype)initWithFrame:(CGRect)r {
 2     self = [super initWithFrame:r];
 3     if (self) {
 4         self.linesInProgress = [[NSMutableDictionary alloc] init];
 5         self.finishedLines = [[NSMutableArray alloc] init];
 6         self.backgroundColor = [UIColor grayColor];
 7         self.multipleTouchEnabled = YES;
 8         
 9         UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc]
10                                                        initWithTarget:self
11                                                        action:@selector(doubleTap:)];
12         doubleTapRecognizer.numberOfTapsRequired = 2;
13         doubleTapRecognizer.delaysTouchesBegan = YES;
14         [self addGestureRecognizer:doubleTapRecognizer];
15         
16         UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
17                                                  initWithTarget:self
18                                                  action:@selector(tap:)];
19         tapRecognizer.delaysTouchesBegan = YES;
20         [tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer]; //区别点击与双击手势
21         [self addGestureRecognizer:tapRecognizer];
22         
23         UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc]
24                                                          initWithTarget:self
25                                                          action:@selector(longPress:)];
26         [self addGestureRecognizer:pressRecognizer];
27     }
28     return self;
29 }

  手势识别器处理长按时,其state属性会经历三种变化,其为 UIGestureRecognizerStatePossible  UIGestureRecognizerStateBegan  UIGestureRecognizerStateEnded 。

实现长按的响应方法longPress:如下:

 1 - (void)longPress:(UIGestureRecognizer *)gr {
 2     if (gr.state == UIGestureRecognizerStateBegan) {
 3         CGPoint point = [gr locationInView:self];
 4         self.selectedLine = [self lineAtPoint:point];
 5         if (self.selectedLine) {
 6             [self.linesInProgress removeAllObjects];
 7         }
 8     } else if (gr.state == UIGestureRecognizerStateEnded) {
 9             self.selectedLine = nil;
10     }
11     [self setNeedsDisplay];
12 }

 让BNRDrawView遵守UIGestureRecognizerDelegate协议,并添加一个UIPanGestureRecognizer属性,如下:

1 @interface BNRDrawView () <UIGestureRecognizerDelegate>
2 
3 @property (nonatomic, strong) UIPanGestureRecognizer *moveRecognizer;
4 @property (nonatomic, strong) NSMutableDictionary *linesInProgress;
5 @property (nonatomic, strong) NSMutableArray *finishedLines;
6 @property (nonatomic, weak) BNRLine *selectedLine;
7 
8 @end

修改initWithFrame:方法。此处cancelsTouchesInView属性默认为YES,意味着手势识别器将吞没任何它识别出的手势,这样的话,视图将没有机会对UIRespnder方法做出响应。将其设置为NO,这样,手势识别器能识别的触摸事件也会被UIResponder识别。代码如下:

 1 - (instancetype)initWithFrame:(CGRect)r {
 2     self = [super initWithFrame:r];
 3     if (self) {
 4         self.linesInProgress = [[NSMutableDictionary alloc] init];
 5         self.finishedLines = [[NSMutableArray alloc] init];
 6         self.backgroundColor = [UIColor grayColor];
 7         self.multipleTouchEnabled = YES;
 8         
 9         UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc]
10                                                        initWithTarget:self
11                                                        action:@selector(doubleTap:)];
12         doubleTapRecognizer.numberOfTapsRequired = 2;
13         doubleTapRecognizer.delaysTouchesBegan = YES;
14         [self addGestureRecognizer:doubleTapRecognizer];
15         
16         UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
17                                                  initWithTarget:self
18                                                  action:@selector(tap:)];
19         tapRecognizer.delaysTouchesBegan = YES;
20         [tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer]; //区别点击与双击手势
21         [self addGestureRecognizer:tapRecognizer];
22         
23         UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc]
24                                                          initWithTarget:self
25                                                          action:@selector(longPress:)];
26         [self addGestureRecognizer:pressRecognizer];
27         
28         self.moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];
29         self.moveRecognizer.delegate = self;
30         self.moveRecognizer.cancelsTouchesInView = NO;
31         [self addGestureRecognizer:self.moveRecognizer];
32     }
33     return self;
34 }

实现UIGestureRecognizerDelegate协议的gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法,如果该方法返回YES,手势识别器将与其它的识别器分享该触摸事件。如下:

1 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
2     if (gestureRecognizer == self.moveRecognizer) {
3         return YES;
4     }
5     return NO;
6 }

实现移动手势的响应事件moveLine:,如下:

 1 - (void)moveLine:(UIPanGestureRecognizer *)gr {
 2     if (!self.selectedLine) {
 3         return;
 4     }
 5     if (gr.state == UIGestureRecognizerStateChanged) {
 6         CGPoint translation = [gr translationInView:self];
 7         CGPoint begin = self.selectedLine.begin;
 8         CGPoint end = self.selectedLine.end;
 9         begin.x += translation.x;
10         begin.y += translation.y;
11         end.x += translation.x;
12         end.y += translation.y;
13         self.selectedLine.begin = begin;
14         self.selectedLine.end = end;
15         [self setNeedsDisplay];
16         [gr setTranslation:CGPointZero inView:self];
17     }
18 }

 程序代码链接:http://pan.baidu.com/s/1o6Dz5xg

原文地址:https://www.cnblogs.com/wjq-Law/p/4904215.html