《iOS編程(第四版)》Demo:TouchTracker

功能:創(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)?UIViewUIResponder 的子類,所以覆蓋以下四個(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)

TouchTracker

模型對(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、UIViewControllerUIApplicationUIWindow 等都是 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 類的父類,例如 UIButtonUISlider。
  • UIControl 對(duì)象并不是直接向目標(biāo)對(duì)象發(fā)送消息,而是通過(guò) UIApplication 轉(zhuǎn)發(fā)。UIApplication 在轉(zhuǎn)發(fā)源自 UIControl 對(duì)象的消息時(shí),會(huì)先判斷目標(biāo)對(duì)象是不是 nil。如果是 nilUIApplication 就會(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ā)三種不同的事件:
    1. UIGestureRecognizerStatePossible(可能會(huì)發(fā)生)
    2. UIGestureRecognizerStateBegan(長(zhǎng)按開(kāi)始)
    3. 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.minitWithFrame: 方法,創(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];
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容