本節任務:建立一個視圖,讓用戶在視圖上拖動手指來畫線。 app
UIView類可以重載4個方法來處理不一樣的觸摸事件。ide
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event // 一個手指或多個手指觸摸屏幕。ui
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event //一個或多個手指在視圖上移動atom
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event //一個或多個手指擡離屏幕時。spa
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event //系統事件,在觸摸結束前被其打斷。設計
@property(nonatomic, getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled // 默認爲NO,若是想要處理多手指觸摸事件,則需設置爲YES。code
一個UITouch對應在屏幕上的一個手指。若是一個視圖開始擁有觸摸對象,該視圖將擁有觸摸對象的生命週期。你不該該引用觸摸對象。 對象
建立一個新的Single View Application工程,命名爲TouchTracker。blog
首先,須要一個模型對象來描述line。建立一個NSObject的子類,取名爲BNRLine。生命週期
再建立一個UIView的子類,取名爲BNRDrawView。BNRDrawView用來追蹤所有畫好了的線條以及目前正在畫的線條。
再建立一個UIViewController的子類來管理BNRDrawView對象,取名爲BNRDrawViewController。
在BNRLine.h頭文件中,聲明兩個CGPoint屬性,以下:
1 @property (nonatomic) CGPoint begin; 2 @property (nonatomic) CGPoint end;
同時,將 #import <Foundation/Foundation.h> 改成 #import <UIKit/UIKit.h> 。
打開BNRDrawViewController.m文件,重載loadView方法,將BNRDrawView對象設置爲BNRDrawViewController的view。
當請求顯示控制器的view時,可是爲nil,則視圖控制器會自動調用loadView方法。該方法應加載或建立一個視圖,並將其賦給控制器的view屬性。若是視圖控制器有一個相對於的nib文件,loadView方法將從該nib文件加載view。若是你使用了Interface Builder建立了視圖並初始化了視圖控制器,你必定不能重載該方法。
若是使用Interface Builder設計用戶界面時,當視圖對象從nib文件中加載視圖時,initWithFrame:方法將不會被調用。
1 - (void)loadView { 2 self.view = [[BNRDrawView alloc] initWithFrame:CGRectZero]; 3 }
同時,在實現文件頂部導入BNRDrawView頭文件,以下:
#import "BNRDrawView.h"
打開AppDelegate.m文件,建立一個BNRDrawViewController對象,將其做爲window的rootViewController。同時導入BNRDrawViewController的頭文件, #import "BNRDrawViewController.h" 。
方法application:didFinishLaunchingWithOptions:方法修改以下:
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 2 // Override point for customization after application launch. 3 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 4 BNRDrawViewController *dvc = [[BNRDrawViewController alloc] init]; 5 self.window.rootViewController = dvc; 6 7 self.window.backgroundColor = [UIColor yellowColor]; 8 [self.window makeKeyAndVisible]; 9 return YES; 10 }
打開BNRDrawView.m,導入BNRLine頭文件,並在類擴展中聲明兩個屬性,以下:
1 #import "BNRLine.h" 2 3 @interface BNRDrawView () 4 5 @property (nonatomic, strong) BNRLine *currentLine; 6 @property (nonatomic, strong) NSMutableArray *finishedLines; 7 8 @end
同時,添加initWithFrame:代碼以下:
1 - (instancetype)initWithFrame:(CGRect)r { 2 self = [super initWithFrame:r]; 3 if (self) { 4 self.finishedLines = [[NSMutableArray alloc] init]; 5 self.backgroundColor = [UIColor grayColor]; 6 } 7 return self; 8 }
同時,須要實現對finishedLine和currentLine的繪製。
當一個視圖第一次顯示或者一個事件的發生使得視圖可見的部分失效了,則drawRect:方法會被調用。絕對不能直接調用該方法。爲了使失效的部分從新繪製,需調用setNeedsDisplay或setNeedsDisplayInRect:方法。以下:
1 - (void)strokeLike:(BNRLine *)line { 2 UIBezierPath *bp = [UIBezierPath bezierPath]; 3 bp.lineWidth = 10; 4 bp.lineCapStyle = kCGLineCapRound; 5 6 [bp moveToPoint:line.begin]; 7 [bp addLineToPoint:line.end]; 8 [bp stroke]; 9 } 10 11 - (void)drawRect:(CGRect)rect { 12 [[UIColor blackColor] set]; 13 for (BNRLine *line in self.finishedLines) { 14 [self strokeLike:line]; 15 } 16 if (self.currentLine) { 17 [[UIColor redColor] set]; 18 [self strokeLike:self.currentLine]; 19 } 20 }
該工程的對象關係以下所示:
當觸摸事件開始時,須要建立一個line,將begin和end設置爲觸摸開始的點位置處。當手指移動時,將更新end。所以,打開BNRDrawView.m文件,touchesBegin:withEvent:方法實現以下:
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 UITouch *t = [touches anyObject]; 3 CGPoint location = [t locationInView:self]; 4 self.currentLine = [[BNRLine alloc] init]; 5 self.currentLine.begin = location; 6 self.currentLine.end = location; 7 [self setNeedsDisplay]; 8 }
touchesMoved:withEvent:方法實現以下:
1 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 UITouch *t = [touches anyObject]; 3 CGPoint location = [t locationInView:self]; 4 self.currentLine.end = location; 5 [self setNeedsDisplay]; 6 }
當觸摸事件結束時,將currentLine添加到finishedLines,touchesEnded:withEvent:實現以下:
1 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 [self.finishedLines addObject:self.currentLine]; 3 self.currentLine = nil; 4 [self setNeedsDisplay]; 5 }
啓動程序,當正在畫線時,線條顯示紅色。當畫線結束,將變爲黑色。
默認狀況下,一個視圖將一次只接受一個觸摸。
此時,須要一個屬性來包含儘量多的將在屏幕上畫出的線條。打開BNRDrawView.m,用下列的屬性代替currentLine。
@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
修改BNRDrawView中initWithFrame:方法使其能接收多點觸摸,並初始化linesInProgress屬性,以下:
1 - (instancetype)initWithFrame:(CGRect)r { 2 self = [super initWithFrame:r]; 3 if (self) { 4 self.linesInProgress = [[NSMutableDictionary alloc] init]; 5 self.finishedLines = [[NSMutableArray alloc] init]; 6 self.backgroundColor = [UIColor grayColor]; 7 self.multipleTouchEnabled = YES; 8 } 9 return self; 10 }
修改觸摸響應事件以下:
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 NSLog(@"%@", NSStringFromSelector(_cmd)); 3 4 for (UITouch *t in touches) { 5 CGPoint location = [t locationInView:self]; 6 BNRLine *line = [[BNRLine alloc] init]; 7 line.begin = location; 8 line.end = location; 9 NSValue *key = [NSValue valueWithNonretainedObject:t]; 10 self.linesInProgress[key] = line; 11 } 12 [self setNeedsDisplay]; 13 } 14 15 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 16 NSLog(@"%@", NSStringFromSelector(_cmd)); 17 18 for (UITouch *t in touches) { 19 NSValue *key = [NSValue valueWithNonretainedObject:t]; 20 BNRLine *line = self.linesInProgress[key]; 21 line.end = [t locationInView:self]; 22 } 23 24 [self setNeedsDisplay]; 25 } 26 27 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 28 NSLog(@"%@", NSStringFromSelector(_cmd)); 29 30 for (UITouch *t in touches) { 31 NSValue *key = [NSValue valueWithNonretainedObject:t]; 32 BNRLine *line = self.linesInProgress[key]; 33 [self.finishedLines addObject:line]; 34 [self.linesInProgress removeObjectForKey:key]; 35 } 36 [self setNeedsDisplay]; 37 }
其中,咱們使用valueWithNonretainedObject:方法來產生key用於保存BNRLine。該方法產生的NSValue對象將擁有與該lian有關的UITouch對象的地址。在每次觸摸事件中。UITouch對象的地址是保持不變的。
在一個NSDictionary中,object使用的key必須遵照NSCopying協議,該協議容許他們接收copy方法。
最後,修改drawRect:方法:
1 - (void)drawRect:(CGRect)rect { 2 [[UIColor blackColor] set]; 3 for (BNRLine *line in self.finishedLines) { 4 [self strokeLike:line]; 5 } 6 [[UIColor redColor] set]; 7 for (NSValue *key in self.linesInProgress) { 8 [self strokeLike:self.linesInProgress[key]]; 9 } 10 }
最後,添加touchesCancelled:withEvent:方法。當一個觸摸事件取消時,應將在linesInProgress中的線條所有移除。以下:
1 - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 NSLog(@"%@", NSStringFromSelector(_cmd)); 3 for (UITouch *t in touches) { 4 NSValue *key = [NSValue valueWithNonretainedObject:t]; 5 [self.linesInProgress removeObjectForKey:key]; 6 } 7 [self setNeedsDisplay]; 8 }