功能:創(chuàng)建一個(gè)畫(huà)板應(yīng)用 TouchTracker,用戶可以在該視圖上觸摸并繪制線條。借助多點(diǎn)觸摸,用戶可以同時(shí)畫(huà)多根線條。
要點(diǎn):觸摸事件、UIGestureRecognizer 手勢(shì)處理、UIMenuController 菜單項(xiàng)、調(diào)試應(yīng)用程序。
1. 觸摸事件與UIResponder
因?yàn)?UIView 是 UIResponder 的子類,所以覆蓋以下四個(gè)方法就可以處理四種不同的觸摸事件:
// 一根手指或多根手指觸摸屏幕
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根手指或多根手指在屏幕上移動(dòng)(隨著手指的移動(dòng),相關(guān)的對(duì)象會(huì)持續(xù)發(fā)送該消息)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根手指或多根手指離開(kāi)屏幕
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 在觸摸操作正常結(jié)束前,某個(gè)系統(tǒng)事件(例如突然來(lái)電話)打斷了觸摸過(guò)程
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
當(dāng)系統(tǒng)檢測(cè)到手指觸摸屏幕的事件后,就會(huì)創(chuàng)建 UITouch 對(duì)象(一根手指的觸摸事件對(duì)應(yīng)一個(gè) UITouch 對(duì)象)。發(fā)生觸摸事件的 UIView 對(duì)象會(huì)收到不同階段的以上四種消息。
UITouch 對(duì)象和事件響應(yīng)的方法的工作機(jī)制:
- 一個(gè)
UITouch對(duì)象對(duì)應(yīng)屏幕上的一根手指。只要手指沒(méi)有離開(kāi)屏幕,相應(yīng)的UITouch對(duì)象就會(huì)一直存在。這些UITouch對(duì)象都會(huì)保存對(duì)應(yīng)的手指在屏幕上的當(dāng)前位置。 - 在觸摸事件的持續(xù)過(guò)程中,無(wú)論發(fā)生什么,最初發(fā)生觸摸事件的那個(gè)視圖都會(huì)在各個(gè)階段收到相應(yīng)的觸摸消息。也就是說(shuō),當(dāng)某個(gè)視圖發(fā)生觸摸事件后,該視圖將永遠(yuǎn)“擁有”當(dāng)時(shí)創(chuàng)建的所有
UITouch對(duì)象。 - 讀者自己編寫(xiě)的代碼不需要也不應(yīng)該保留任何
UITouch對(duì)象。當(dāng)某個(gè)UITouch對(duì)象的狀態(tài)發(fā)生變化時(shí),系統(tǒng)會(huì)向指定的對(duì)象發(fā)送特定的事件消息,并傳入發(fā)生變化的UITouch對(duì)象。 - 觸摸事件由系統(tǒng)添加至一個(gè)由
UIApplication單例管理的事件隊(duì)列。 - 當(dāng)多根手指在同一個(gè)視圖、同一個(gè)時(shí)刻執(zhí)行相同的觸摸動(dòng)作時(shí),
UIApplication會(huì)用單個(gè)消息、一次分發(fā)所有相關(guān)的UITouch對(duì)象。但是,因?yàn)?UIApplication對(duì)“同一時(shí)刻”的判斷很嚴(yán)格,通常UIApplication都是發(fā)送多個(gè)UIResponder消息,分批發(fā)送UITouch對(duì)象。
2. 創(chuàng)建 TouchTracker 畫(huà)板應(yīng)用

項(xiàng)目的組織結(jié)構(gòu)

模型對(duì)象:定義一條線
通過(guò)兩個(gè)點(diǎn)可以定義一條直線,所以 HQLLine 對(duì)象需要用 begin 屬性和 end 屬性來(lái)保存起點(diǎn)和終點(diǎn)。
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
/**
描述線條的模型對(duì)象:起點(diǎn) begin(x,y)、終點(diǎn) end(x,y)
*/
@interface HQLLine : NSObject
@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;
@end
3. 實(shí)現(xiàn)繪圖功能
HQLDrawView 對(duì)象要實(shí)現(xiàn)繪圖功能:
管理正在繪制的線條、被選中的線條和繪制完成的線條。所以在類擴(kuò)展中添加這幾個(gè)屬性:
@interface HQLDrawView () <UIGestureRecognizerDelegate>
// 保存當(dāng)前線(多點(diǎn)觸控,同時(shí)保存多線)
// key 是 UITouch 對(duì)象的內(nèi)存地址,value 是 HQLLine 對(duì)象
@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
// 保存所有線
@property (nonatomic, strong) NSMutableArray *finishedLines;
// 單擊手勢(shì)選中的線條
// weak: ① finishedLines 數(shù)組會(huì)保存 selectedLine,是強(qiáng)引用;
// ② 如果用戶雙擊清除所有線條,finishedLines 數(shù)組會(huì)移除 selectedLine,這時(shí) HQLDrawView會(huì)自動(dòng)將 selectedLine 設(shè)置為 nil
@property (nonatomic, weak) HQLLine *selectedLine;
@end
說(shuō)明:
- 當(dāng)前線
linesInProgress通過(guò)NSMutableDictionary字典來(lái)保存,字典的鍵是一個(gè)NSValue對(duì)象,用于保存UITouch對(duì)象的內(nèi)存地址。字典的值就是UITouch對(duì)象所對(duì)應(yīng)的線條模型對(duì)象(即HQLLine對(duì)象)。 - 使用內(nèi)存地址分辨
UITouch對(duì)象的原因是:在觸摸事件開(kāi)始、移動(dòng)、結(jié)束的整個(gè)過(guò)程中,其內(nèi)存地址是不會(huì)改變的,內(nèi)存地址相同的UITouch對(duì)象一定是同一個(gè)對(duì)象。 - 使用
NSValue對(duì)象封裝UITouch對(duì)象的內(nèi)存地址,而不是直接保存UITouch對(duì)象。是因?yàn)椋?code>NSDictionary 及其子類NSMutableDictionary的鍵必須遵守<NSCopying>協(xié)議——鍵必須可以復(fù)制(可以響應(yīng)copy消息)。UITouch并不遵守<NSCopying>協(xié)議,因?yàn)槊恳粋€(gè)觸摸事件都是唯一的,不應(yīng)該被復(fù)制。相反,NSValue遵守<NSCopying>協(xié)議,同一個(gè)UITouch對(duì)象會(huì)在觸摸過(guò)程中創(chuàng)建包含相同內(nèi)存地址的NSValue對(duì)象。
處理觸摸事件:
#pragma mark 一根手指或多根手指觸摸屏幕
// 觸摸事件開(kāi)始時(shí),HQLDrawView 對(duì)象需要?jiǎng)?chuàng)建一個(gè) HQLLine 對(duì)象,并將其begin屬性和end屬性設(shè)置為觸摸發(fā)生時(shí)的手指位置
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 向控制臺(tái)輸出日志,查看觸摸事件發(fā)生順序
NSLog(@"%@",NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
// 根據(jù)觸摸位置創(chuàng)建 HQLLine 對(duì)象
CGPoint location = [t locationInView:self];
HQLLine *line = [[HQLLine alloc] init];
line.begin = location;
line.end = location;
// valueWithNonretainedObject:將 UITouch 對(duì)象的內(nèi)存地址封裝為 NSValue 對(duì)象
// 使用內(nèi)存地址分辨 UITouch 對(duì)象的原因是,在觸摸事件開(kāi)始、移動(dòng)、結(jié)束的整個(gè)過(guò)程中,其內(nèi)存地址不會(huì)改變,內(nèi)存地址相同的 UITouch 對(duì)象一定是同一個(gè)對(duì)象。
NSValue *key = [NSValue valueWithNonretainedObject:t];
// 保存當(dāng)前線到字典中,key值是內(nèi)存地址,value值是 HQLLine 對(duì)象
self.linesInProgress[key] = line;
}
[self setNeedsDisplay];
}
#pragma mark 一根手指或多根手指在屏幕上移動(dòng)
// 觸摸事件繼續(xù)時(shí)(手指在頻幕上移動(dòng)),HQLDrawView對(duì)象需要將end設(shè)置為手指的當(dāng)前位置
- (void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 向控制臺(tái)輸出日志,查看觸摸事件發(fā)生順序
NSLog(@"%@",NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
// 根據(jù)當(dāng)前 UITouch 對(duì)象的內(nèi)存地址找到key值,再找到value值,更新 HQLLine 對(duì)象的終點(diǎn)
NSValue *key = [NSValue valueWithNonretainedObject:t];
HQLLine *line = self.linesInProgress[key];
line.end = [t locationInView:self];
}
[self setNeedsDisplay];
}
#pragma mark 一根手指或多根手指離開(kāi)屏幕
// 觸摸結(jié)束時(shí),將當(dāng)前線linesInProgress保存到finishedLines數(shù)組中。
- (void) touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 向控制臺(tái)輸出日志,查看觸摸事件發(fā)生順序
NSLog(@"%@",NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithNonretainedObject:t];
HQLLine *line = self.linesInProgress[key];
// 將所有繪制完成的線,即 HQLLine 對(duì)象添加到_finishedLines數(shù)組中
[self.finishedLines addObject:line];
// 從當(dāng)前線中移除 HQLLine 對(duì)象
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
}
#pragma mark 在觸摸操作正常結(jié)束前,某個(gè)系統(tǒng)事件打斷了觸摸進(jìn)程
// 觸摸取消事件:當(dāng)系統(tǒng)中斷了應(yīng)用,觸摸事件就會(huì)被取消,這是應(yīng)用應(yīng)該恢復(fù)到觸摸事件發(fā)生前的狀態(tài),這里就簡(jiǎn)單的清楚所有正在繪制的線條。
- (void) touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//向控制臺(tái)輸出日志,查看觸摸事件發(fā)生順序
NSLog(@"%@",NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithNonretainedObject:t];
// 從當(dāng)前線中移除 HQLLine 對(duì)象
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
}
8. 響應(yīng)對(duì)象鏈
UIResponder 對(duì)象可以接受觸摸事件,而 UIView、UIViewController、UIApplication 和 UIWindow 等都是 UIResponder 的子類。因此,他們都可以可以接受觸摸事件。
例如,雖然 UIViewController 不是視圖對(duì)象,系統(tǒng)不能向 UIViewController 對(duì)象直接發(fā)送觸摸事件,但是該對(duì)象能通過(guò)響應(yīng)對(duì)象鏈接受事件。
UIResponder 對(duì)象擁有一個(gè)名為 nextResponder 的指針,相關(guān)的 UIResponder 對(duì)象可以通過(guò)該指針組成一個(gè)響應(yīng)對(duì)象鏈。
- 當(dāng)
UIView對(duì)象屬于某個(gè)UIViewController對(duì)象時(shí),其nextResponder指針就會(huì)指向包含該視圖的UIViewController對(duì)象。 - 當(dāng)
UIView對(duì)象不屬于任何UIViewController對(duì)象時(shí),其nextResponder指針就會(huì)指向該視圖的父視圖。UIViewController對(duì)象的nextResponder通常會(huì)指向其視圖的父視圖。最頂層的父視圖是UIWindow對(duì)象,而UIWindow對(duì)象的nextResponder指向的是UIApplication單例。 - 如果沒(méi)有為某個(gè)
UIResponder對(duì)象覆蓋特定的事件處理方法,那么該對(duì)象的nextResponder會(huì)嘗試處理相應(yīng)的觸摸事件。最終,該事件會(huì)傳遞給UIApplication(響應(yīng)對(duì)象鏈的最后一個(gè)對(duì)象),如果UIApplication也無(wú)法對(duì)其處理,系統(tǒng)就會(huì)丟棄該事件。
除了由 UIResponder 對(duì)象向 nextResponder 轉(zhuǎn)發(fā)消息,也可以直接向 nextResponder 發(fā)送消息。
[[self nextResponder]touchesBegan:touches withEvent:event];
UIController
-
UIController是部分 Cocoa Touch 類的父類,例如UIButton和UISlider。 -
UIControl對(duì)象并不是直接向目標(biāo)對(duì)象發(fā)送消息,而是通過(guò)UIApplication轉(zhuǎn)發(fā)。UIApplication在轉(zhuǎn)發(fā)源自UIControl對(duì)象的消息時(shí),會(huì)先判斷目標(biāo)對(duì)象是不是nil。如果是nil,UIApplication就會(huì)先找出UIWindow對(duì)象的第一響應(yīng)對(duì)象,然后向第一響應(yīng)對(duì)象發(fā)送相應(yīng)的動(dòng)作消息。
13. UIGestureRecognizer 與 UIMenuController
-
UIGestureRecognizer對(duì)象可以用于識(shí)別特定手勢(shì),例如“張開(kāi)或者合攏手指”(pinch)或者滑動(dòng)(swipe)手勢(shì)。 - 在為應(yīng)用添加手勢(shì)識(shí)別功能時(shí),需要針對(duì)特定的手勢(shì)創(chuàng)建
UIGestureRecognizer子類對(duì)象,而不是直接使用UIGestureRecognizer對(duì)象。
雙擊手勢(shì)——UITapGestureRecognize
添加雙擊手勢(shì)需要在 HQLDrawView.m 的初始化方法中創(chuàng)建一個(gè) UITapGestureRecognize 對(duì)象,并注冊(cè)觸發(fā)的手勢(shì)方法。
初始化創(chuàng)建雙擊手勢(shì):
// 使用 UIGestureRecognize 子類步驟:
// ① 創(chuàng)建對(duì)象并設(shè)置目標(biāo)-動(dòng)作對(duì);
// ② 將該對(duì)象“附著”在某個(gè)視圖上;
// UIGestureRecognize 子類 - UITapGestureRecognizer
// 1?? 雙擊手勢(shì)
UITapGestureRecognizer *doubleTapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(doubleTap:)];
// 設(shè)置點(diǎn)擊次數(shù)為2
doubleTapRecognizer.numberOfTapsRequired = 2;
// 設(shè)置手勢(shì)優(yōu)先:延遲 UIView 收到 UIResponder 消息
// 只有雙擊手勢(shì)識(shí)別失敗后,才能再去識(shí)別 UIResponder 消息的觸摸手勢(shì),防止誤識(shí)別。
doubleTapRecognizer.delaysTouchesBegan = YES;
// 將該對(duì)象“附著”在視圖上;
[self addGestureRecognizer:doubleTapRecognizer];
觸發(fā)響應(yīng)事件:
// doubleTap:雙擊手勢(shì)方法:刪除所有線條
- (void) doubleTap:(UIGestureRecognizer *)gr {
NSLog(@"Recognized Double Tap");
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
}
單擊手勢(shì)——UITapGestureRecognizer
在 HQLDrawView.m 的初始化方法中創(chuàng)建一個(gè) UITapGestureRecognizer 單擊手勢(shì)對(duì)象。
初始化創(chuàng)建雙擊手勢(shì):
// 2?? 單擊手勢(shì)
UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tap:)];
// 同樣設(shè)置手勢(shì)優(yōu)先:① 延遲 UIView 收到 UIResponder 消息
tapRecognizer.delaysTouchesBegan = YES;
// ② 需要雙擊手勢(shì)識(shí)別失敗時(shí)再識(shí)別單擊手勢(shì)
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
[self addGestureRecognizer:tapRecognizer];
觸發(fā)響應(yīng)事件:
#pragma mark - 單擊手勢(shì)
- (void) tap:(UIGestureRecognizer *)gr {
NSLog(@"Recognized tap");
// 獲取點(diǎn)擊的坐標(biāo)位置
CGPoint point = [gr locationInView:self];
// 根據(jù)該點(diǎn)找到最近的線
self.selectedLine = [self lineAtPoint:point];
// 彈出刪除菜單
if (self.selectedLine) {
// 使視圖成為 UIMenuItem 動(dòng)作消息的目標(biāo)
[self becomeFirstResponder];
// 獲取 UIMenuController 對(duì)象
UIMenuController *menu = [UIMenuController sharedMenuController];
// 創(chuàng)建一個(gè)新的標(biāo)題為 “Delete” 的 UIMenuItem 對(duì)象
UIMenuItem *deleteItem =
[[UIMenuItem alloc] initWithTitle:@"刪除"
action:@selector(deleteLine:)];
menu.menuItems = @[deleteItem];
// 先為 UIMenuController 對(duì)象設(shè)置顯示區(qū)域,然后將其設(shè)置為可見(jiàn)
[menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
[menu setMenuVisible:YES animated:YES];
}else {
// 如果沒(méi)有選中的線條,就隱藏 UIMenuController 對(duì)象
// 即點(diǎn)擊其他空白區(qū)域,隱藏 UIMenuController 對(duì)象
[[UIMenuController sharedMenuController] setMenuVisible:NO
animated:YES];
}
// 用綠色重繪這根線條
[self setNeedsDisplay];
}
#pragma 根據(jù)點(diǎn)找出最近的線
- (HQLLine *) lineAtPoint:(CGPoint) p {
// 找出離P最近的 HQLLine 對(duì)象
for (HQLLine *l in self.finishedLines) {
CGPoint start = l.begin;
CGPoint end = l.end;
//檢查線條的若干點(diǎn)
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);
// 如果線條的某個(gè)點(diǎn)和p的距離在20點(diǎn)以內(nèi),就返回相應(yīng)的HQLLine對(duì)象
if (hypot(x-p.x, y-p.y) < 20.0) {
return l;
}
}
}
return nil;
}
#pragma UIMenuItem 動(dòng)作方法:刪除線
- (void) deleteLine:(id) sender {
// 從已經(jīng)完成的線條中刪除選中的線條
[self.finishedLines removeObject:self.selectedLine];
// 重畫(huà)整個(gè)視圖
[self setNeedsDisplay];
}
UIMenuController
以上單擊手勢(shì)方法 tap: 中用到了 UIMenuController 對(duì)象
-
UIMenuController對(duì)象用于在用戶手指按下的地方顯示一組UIMenuItem對(duì)象(菜單項(xiàng)),每個(gè)UIMenuItem對(duì)象都有自己的標(biāo)題和動(dòng)作方法。 - 顯示
UIMenuItem對(duì)象的對(duì)象必須是當(dāng)前UIWindow對(duì)象的第一響應(yīng)對(duì)象。
如果要將某個(gè)自定義的 UIView 子類對(duì)象設(shè)置為第一響應(yīng)者 [self becomeFirstResponder],就必須在 HQLDrawView.m 中覆蓋 canBecomeFirstResponder 方法并返回 YES:
- (BOOL) canBecomeFirstResponder {
return YES;
}
還需要為 UIMenuController 對(duì)象實(shí)現(xiàn)刪除線的動(dòng)作方法,才會(huì)在選中某條線時(shí)顯示 UIMenuController 對(duì)象。如果 UIMenuController 對(duì)象的動(dòng)作方法沒(méi)有實(shí)現(xiàn),應(yīng)用就不會(huì)顯示 UIMenucontroller 對(duì)象。
- (void) deleteLine:(id) sender {
//從已經(jīng)完成的線條中刪除選中的線條
[self.finishedLines removeObject:self.selectedLine];
//重畫(huà)整個(gè)視圖
[self setNeedsDisplay];
}
長(zhǎng)按手勢(shì)——UILongPressGestureRecognizer
-
UILongPressGestureRecognizer對(duì)象默認(rèn)會(huì)將持續(xù)時(shí)間超過(guò)0.5秒的觸摸事件識(shí)別為長(zhǎng)按手勢(shì)。設(shè)置UILongPressGestureRecognizer對(duì)象的minimumPressDuration屬性可以修改這個(gè)時(shí)間。 -
UILongPressGestureRecognizer對(duì)象在識(shí)別出長(zhǎng)按手勢(shì)后,會(huì)持續(xù)跟蹤該手勢(shì)并在不同的階段分別觸發(fā)三種不同的事件:-
UIGestureRecognizerStatePossible(可能會(huì)發(fā)生) -
UIGestureRecognizerStateBegan(長(zhǎng)按開(kāi)始) -
UIGestureRecognizerStateEnded(長(zhǎng)按結(jié)束)
-
在 HQLDrawView.m 的初始化方法中創(chuàng)建一個(gè) UILongPressGestureRecognizer 長(zhǎng)按手勢(shì)對(duì)象。
// UIGestureRecognize 子類 - UILongPressGestureRecognizer
// 3?? 長(zhǎng)按手勢(shì)
UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPress:)];
// UILongPressGestureRecognizer 對(duì)象默認(rèn)會(huì)將持續(xù)時(shí)間超過(guò) 0.5秒的觸摸事件識(shí)別為長(zhǎng)按手勢(shì)
// 設(shè)置 minimumPressDuration 屬性可修改該時(shí)間
pressRecognizer.minimumPressDuration = 0.6;
[self addGestureRecognizer:pressRecognizer];
在 HQLDrawView.m 中實(shí)現(xiàn) longPress: 方法
- (void) longPress:(UIGestureRecognizer *)gr {
// 長(zhǎng)按開(kāi)始
if (gr.state == UIGestureRecognizerStateBegan) {
CGPoint point = [gr locationInView:self];
// 選中被長(zhǎng)按的線
self.selectedLine = [self lineAtPoint:point];
if (self.selectedLine) {
// 刪除當(dāng)前線
[self.linesInProgress removeAllObjects];
}
}
// 長(zhǎng)按結(jié)束
else if (gr.state == UIGestureRecognizerStateEnded) {
self.selectedLine = nil;
}
[self setNeedsDisplay];
}
UIPanGestureRecognizer(拖動(dòng)手勢(shì))以及同時(shí)識(shí)別多個(gè)手勢(shì)
在 HQLDrawView.m 中的類擴(kuò)展中將 HQLDrawView 聲明為遵守 <UIGestureRecognizerDelegate> 協(xié)議。然后聲明一個(gè)類型為 UIPanGestureRecognizer 的屬性:
@property (nonatomic,strong) UIPanGestureRecognizer *moveRecognizer;
更新 HQLDrawView.m 的 initWithFrame: 方法,創(chuàng)建一個(gè) UIPanGestureRecognizer 對(duì)象。默認(rèn)情況下,UIGestureRecognize 對(duì)象在識(shí)別出特定的手勢(shì)時(shí),會(huì)“吃掉”所有和該手勢(shì)有段的 UItouch 對(duì)象,這邊選擇否“NO”,讓 UILongPressGestureRecognizer 對(duì)象和 UIPanGestureRecognizer 對(duì)象能夠同時(shí)識(shí)別手勢(shì)。
// 4?? 移動(dòng)手勢(shì)
self.moveRecognizer =
[[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(moveLine:)];
self.moveRecognizer.delegate = self;
// cancelsTouchesInView 屬性默認(rèn)為 YES,這個(gè)對(duì)象會(huì)在識(shí)別出特定手勢(shì)時(shí),“吃掉”所有和該手勢(shì)有關(guān)的 UITouch 對(duì)象;
// 這里設(shè)置為NO,因?yàn)檫€要處理相關(guān)的 UITouch 對(duì)象:
// 一開(kāi)始,畫(huà)板上沒(méi)有任何線時(shí),在屏幕上劃動(dòng)就會(huì)識(shí)別出 moveRecognizer 手勢(shì),它會(huì)發(fā)送 moveLine:消息。執(zhí)行該消息,如果沒(méi)有選中的線條就直接返回。與此同時(shí) YES 的cancelsTouchesInView 屬性攔截了所有的 UIResponder 方法,就無(wú)法處理 UITouch 對(duì)象,就無(wú)法畫(huà)線。
self.moveRecognizer.cancelsTouchesInView = NO;
[self addGestureRecognizer:self.moveRecognizer];
使用 <UIGestureRecognizerDelegate> 協(xié)議中的一個(gè)協(xié)議 gestureRecognizer: shouldRecognizeSimultaneouslyWithGestureRecognizer:,當(dāng)某個(gè) UIGestureRecognizer 子類對(duì)象識(shí)別出特定的手勢(shì)后,如果發(fā)現(xiàn)其他的 UIGestureRecognizer 子類對(duì)象也識(shí)別出了特定的手勢(shì),就會(huì)向其委托對(duì)象發(fā)送 gestureRecognizer: shouldRecognizeSimultaneouslyWithGestureRecognizer: 消息。如果相應(yīng)的方法返回YES,那么當(dāng)前的 UIGestureRecognizer 子類對(duì)象就會(huì)和其他的 UIGestureRecognizer 子類對(duì)象共享 UITouch 對(duì)象。
#pragma mark - UIGestureRecognizerDelegate
// 默認(rèn)情況下,UIGestureRecognize 對(duì)象在識(shí)別出特定的手勢(shì)時(shí),會(huì)“吃掉”所有和該手勢(shì)有關(guān)的 UItouch 對(duì)象
// 讓 UILongPressGestureRecognizer 長(zhǎng)按手勢(shì)和 UIPanGestureRecognizer 拖動(dòng)手勢(shì)同時(shí)被識(shí)別
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer == self.moveRecognizer) {
return YES;
}
return NO;
}
在 HQLDrawView.m 中實(shí)現(xiàn) moveLine: 方法:
- (void) moveLine:(UIPanGestureRecognizer *)gr {
// 如果沒(méi)有選中的線條就直接返回
if (! self.selectedLine) {
return;
}
// 如果菜單項(xiàng)可見(jiàn),返回
UIMenuController *menuController = [UIMenuController sharedMenuController];
if (menuController.isMenuVisible ) {
return;
}
// 如果 UIPanGestureRecognizer 對(duì)象處于“變化后”狀態(tài)
if (gr.state == UIGestureRecognizerStateChanged) {
// 獲取手指的拖移距離
CGPoint translation = [gr translationInView:self];
// 將拖移距離加至選中的線條的起點(diǎn)和終點(diǎn)
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;
// 為選中的線段設(shè)置新的起點(diǎn)和和終點(diǎn)
self.selectedLine.begin = begin;
self.selectedLine.end = end;
// 重畫(huà)視圖
[self setNeedsDisplay];
// 使該對(duì)象增量地報(bào)告拖移距離
// :將手指的當(dāng)前位置設(shè)置為拖移手勢(shì)的起始位置
[gr setTranslation:CGPointZero inView:self];
}
}