首先說一下實現的原理, 首先當手指開始觸摸屏幕 以及滑動的時候, 效果與畫矩形框是一樣的 因此 此時的代碼也機會沒有區別,
當手指松開後 在當前的矩形框處創建一個臨時的textView ,並且背景變為灰色,textView 編輯結束, 在textView的 完成委托方法中 去掉灰色的背景 去掉臨時的textView 在相同的位置上 利用coreText 顯示出剛才編輯的內容
首先 手指觸摸 會調用到
TextTool.m
?
1
2
3
4
5
6
7
8
9
10
11
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIView *touchedView = [delegate viewForUseWithTool:self];
//注意下面這句代碼
[touchedView endEditing:YES];
// we only track one touch at a time for this tool.
UITouch *touch = [[event allTouches] anyObject];
// remember the touch, and its original start point, for future
[trackingTouches addObject:touch];
CGPoint location = [touch locationInView:touchedView];
[startPoints addObject:[NSValue valueWithCGPoint:location]];
}
代碼 除了注釋的那一句 基本上與前面的矩形 是相同的 紀錄下來 UITouch 的實例 以及 開始的點
而注釋那一句 得到的touchedView 就是dudelView 如果此處設置為yes 在textView 成為第一響應 彈出鍵盤後,我們可以直接觸摸view 來取消TextView的第一響應狀態
隨著手指移動 不斷調用到 touchMoved 但是 與前面矩形的實現一樣 這裡的touchMoved方法為空 畫出臨時矩形的實現代碼在
drawTemporary 方法中
TextTool.m
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)drawTemporary {
if (self.completedPath) {
[delegate.strokeColor setStroke];
[self.completedPath stroke];
} else {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (int i = 0; i<[trackingTouches count]; i++) {
UITouch *touch = [trackingTouches objectAtIndex:i];
CGPoint startPoint = [[startPoints objectAtIndex:i] CGPointValue];
CGPoint endPoint = [touch locationInView:touchedView];
CGRect rect = CGRectMake(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:rect];
[delegate.strokeColor setStroke];
[path stroke];
}
}
}
completedPath是一個UIBezierPath 的實例, 在touchEnded方法中 將最終矩形的路徑 設置到其中, 所以在手指抬起來以前 都只會進入到else分支, 根據 起點與當前手指觸摸的點 描出一個矩形來
當手指抬起來後 進入touchEnded方法,而主要的實現代碼就在這裡面
TextTool.m
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (UITouch *touch in [event allTouches]) {
// make a rect from the start point to the current point
NSUInteger touchIndex = [trackingTouches indexOfObject:touch];
// only if we actually remember the start of this touch...
if (touchIndex != NSNotFound) {
CGPoint startPoint = [[startPoints objectAtIndex:touchIndex] CGPointValue];
CGPoint endPoint = [touch locationInView:touchedView];
[trackingTouches removeObjectAtIndex:touchIndex];
[startPoints removeObjectAtIndex:touchIndex];
// detect short taps that are too small to contain any text;
// these are probably accidents
if (distanceBetween(startPoint, endPoint) < 50.0) return;
CGRect rect = CGRectMake(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y);
self.completedPath = [UIBezierPath bezierPathWithRect:rect];
// draw a shaded area over the entire view, so that the user can
// easily see where to focus their attention.
UIView *backgroundShade = [[[UIView alloc] initWithFrame:touchedView.bounds] autorelease];
backgroundShade.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
backgroundShade.tag = SHADE_TAG;
backgroundShade.userInteractionEnabled = NO;
[touchedView addSubview:backgroundShade];
// now comes the fun part. we make a temporary UITextView for the
// actual text input.
UITextView *textView = [[[UITextView alloc] initWithFrame:rect] autorelease];
textView.font = delegate.font;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
// in case the chosen view is going to be below the keyboard, we need to
// make an effort to determine how far the display area should slide when
// the keyboard is going to be shown.
//
// keyboard heights:
//
// 352 landscape
// 264 portrait
CGFloat keyboardHeight = 0;
UIInterfaceOrientation orientation = ((UIViewController*)delegate).interfaceOrientation;
if (UIInterfaceOrientationIsPortrait(orientation)) {
keyboardHeight = 264;
} else {
keyboardHeight = 352;
}
CGRect viewBounds = touchedView.bounds;
CGFloat rectMaxY = rect.origin.y + rect.size.height;
CGFloat availableHeight = viewBounds.size.height - keyboardHeight;
if (rectMaxY > availableHeight) {
// calculate a slide distance so that the dragged box is centered vertically
viewSlideDistance = rectMaxY - availableHeight;
} else {
viewSlideDistance = 0;
}
textView.delegate = self;
[touchedView addSubview:textView];
textView.editable = NO;
textView.editable = YES;
[textView becomeFirstResponder];
}
}
}
首先 得到起點,終點,然後清空數組
if (distanceBetween(startPoint, endPoint) < 50.0) return; 進行了一個判斷, 防止用戶畫的矩形太小,影響輸入文字,
如果大小沒問題,接下來 便將最終的矩形路徑 賦值到 self.completedPath 在退出此方法後 進入drawTemporary後就會進入if分支
接著畫出 灰色的背景, 此處的設置透明度為0.5, 並且為背景view設置tag為SHADE_TAG 這個標記用來在以後 編輯完成的時候通過 viewWithTag方法 找到這個背景的實例 並且 移除它
接下來便開始創建textView 其frame就是 剛在最終得到的矩形路徑的位置信息
接著注冊了一些通知, 即在鍵盤彈出,或者落下的時候 調用的幾個方法
下面的一部分代碼實現的功能是 如果鍵盤彈出 可能會擋住 textView 判斷 是否擋住了 如果擋住了 需要向上移動多少距離,並且將這個距離 賦值給全局的 變量viewSlideDistance 如果沒有擋住 設置為0
最後將textView顯示出來,並且通過[textView becomeFirstResponder]; 在其剛顯示的時候 馬上成為第一響應者,彈出鍵盤
下面兩個方法在鍵盤彈出之前以及落下之前,調用 移動視圖,防止textView 被擋
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- (void)keyboardWillShow:(NSNotification *)aNotification {
UIInterfaceOrientation orientation = ((UIViewController*)delegate).interfaceOrientation;
[UIView beginAnimations:@"viewSlideUp" context:NULL];
UIView *view = [delegate viewForUseWithTool:self];
CGRect frame = [view frame];
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
frame.origin.x -= viewSlideDistance;
break;
case UIInterfaceOrientationLandscapeRight:
frame.origin.x += viewSlideDistance;
break;
case UIInterfaceOrientationPortrait:
frame.origin.y -= viewSlideDistance;
break;
case UIInterfaceOrientationPortraitUpsideDown:
frame.origin.y += viewSlideDistance;
break;
default:
break;
}
[view setFrame:frame];
[UIView commitAnimations];
}
- (void)keyboardWillHide:(NSNotification *)aNotification {
UIInterfaceOrientation orientation = ((UIViewController*)delegate).interfaceOrientation;
[UIView beginAnimations:@"viewSlideDown" context:NULL];
UIView *view = [delegate viewForUseWithTool:self];
CGRect frame = [view frame];
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
frame.origin.x += viewSlideDistance;
break;
case UIInterfaceOrientationLandscapeRight:
frame.origin.x -= viewSlideDistance;
break;
case UIInterfaceOrientationPortrait:
frame.origin.y += viewSlideDistance;
break;
case UIInterfaceOrientationPortraitUpsideDown:
frame.origin.y -= viewSlideDistance;
break;
default:
break;
}
[view setFrame:frame];
[UIView commitAnimations];
}
當我們編輯完了內容後 我們可以點擊屏幕的其他地方(我們設置了[dudelView endEditing:YES]) 或者點擊鍵盤上的落下鍵盤按鈕 這時 進入textView的 delegate
?
1
2
3
4
5
6
7
8
9
10
11
- (void)textViewDidEndEditing:(UITextView *)textView {
//NSLog(@"textViewDidEndEditing");
TextDrawingInfo *info = [TextDrawingInfo textDrawingInfoWithPath:completedPath text:textView.text strokeColor:delegate.strokeColor font:delegate.font];
[delegate addDrawable:info];
self.completedPath = nil;
UIView *superView = [textView superview];
[[superView viewWithTag:SHADE_TAG] removeFromSuperview];
[textView resignFirstResponder];
[textView removeFromSuperview];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
這裡 用到了一個新類TextDrawingInfo 同上一章 的PathDrawingInfo 類似,都是 存放一個完整操作的 全部信息,由於上一章我們做的是繪圖操作,因此PathDrawingInfo 存放的是每次繪圖操作的信息, 而這次我們主要向利用coreText在屏幕上顯示文字 那麼之前繪制的矩形的信息 此時已經沒有用 我門要 取得 CoreText 需要的信息,即我們這次文字操作的信息 存到TextDrawingInfo 中去 並且添加到 dudelView 的 drawables中 最後 將沒用的東西全部從屏幕上移除
最後看以下 TextDrawingInfo 中的draw方法是怎麼把文字繪制上去的
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)draw {
CGContextRef context = UIGraphicsGetCurrentContext();
NSMutableAttributedString *attrString = [[[NSMutableAttributedString alloc] initWithString:self.text] autorelease];
[attrString addAttribute:(NSString *)(kCTForegroundColorAttributeName) value:(id)self.strokeColor.CGColor range:NSMakeRange(0, [self.text length])];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attrString length]), self.path.CGPath, NULL);
CFRelease(framesetter);
//CFRelease(attrString);
if (frame) {
CGContextSaveGState(context);
// Core Text wants to draw our text upside-down! This flips it the
// right way.
CGContextTranslateCTM(context, 0, path.bounds.origin.y);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -(path.bounds.origin.y + path.bounds.size.height));
CTFrameDraw(frame, context);
CGContextRestoreGState(context);
CFRelease(frame);
}
}
首先獲得當前的上下文
創建一個屬性自字符串NSMutableAttributedString 並設置他的顏色以及其他屬性
利用該屬性字符串 創建一個CTFramesetterRef
再創建一個CTFrameRef
釋放之前創建的CTFramesetterRef 對象framesetter
由於CoreText 是來自於Mac OS X的 它在繪圖的時候 認為坐標軸是倒置的,所以在沒ios中會產生倒置的效果,這裡要轉化以下才能正常顯示