UIGestureRecognizer 對象會截取本應由視圖處理的觸摸事件。當某個UIGestureRecognizer對象識別出特定的手勢後,就會向指定的對象發送指定的消息。iOS SDK預設提供若幹中UIGestureRecoginezer對象。本章我們將繼續更新 JXTouchTracker ...
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 = 2; [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 = 3; 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 = 2; // 為了避免在識別出點擊手勢之前出發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 = 3; 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 = 2; // 為了避免在識別出點擊手勢之前出發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 = 3; 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 = 2; // 為了避免在識別出點擊手勢之前出發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 = 3; 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 = 2; // 為了避免在識別出點擊手勢之前出發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 = 3; 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 = 2; // 為了避免在識別出點擊手勢之前出發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, 2, 2) 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 = 3; 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 "