iOS UIGestureRecognizer與UIMenuController(內容根據iOS編程)

  UIGestureRecognizer 對象會截取本應由視圖處理的觸摸事件。當某個UIGestureRecognizer對象識別出特定的手勢後,就會向指定的對象發送指定的消息。iOS SDK默認提供若干中UIGestureRecoginezer對象。本章咱們將繼續更新 JXTouchTracker ,藉助由iOS SDK提供的三種 UIGestureRecogniezer對象,用戶能夠選擇、移動、刪除線條。git

  • UIGestureRecognizer子類

  在爲應用添加手勢識別功能時,須要針對特定的手勢建立響應的UIGestureRecognizer子類對象,而不是直接使用UIGestureRecognizer對象。iOS SDK提供了多種可以處理不一樣手勢的UIGestureRecognizer子類。github

  使用UIGestureRecognizer子類對象時,除了要設置目標動做對,還要將該子類對象「附着」在某個視圖上。當該子類對象根據當前附着的視圖所發生的觸摸事件識別出相應的手勢時,就會向指定的目標對象發送指定的動做消息。由UIGestureRecognizer對象發出的動做消息都會遵照如下規範:ui

- (void)action:(UIGestureRecognizer *)gestureRecognizer

  UIGestureRecognizer對象在識別手勢時,會截取本應由其附着的視圖自行處理的觸摸事件。所以,附着了 UIGestureRecognizer 對象的視圖可能不會受到常規的 UIResponder 消息,例如,不會收到: - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 消息。atom

  • 用UITapGestureRecognizer對象識別點擊手勢

  下面爲咱們應用添加一個功能,當用戶雙擊屏幕時,會清除屏幕上的全部線條。spa

#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

  構建並運行,同時咱們能夠檢測觸摸事件發生的順序:3d

- (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 )code

  爲了在識別出點擊手勢以前避免向 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
  • 同時添加多種觸摸手勢

  接下來咱們爲應用中添加單擊手勢,讓用戶能夠選擇屏幕上的線條blog

#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 "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)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 = 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
  •  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 = 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];
        
        // 添加長按手勢
        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, 2, 2) 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 = 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

  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 = 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];
        
        // 添加長按手勢
        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, 2, 2) 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 = 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
相關文章
相關標籤/搜索