首先说一下实现的原理, 首先当手指开始触摸屏幕 以及滑动的时候, 效果与画矩形框是一样的 因此 此时的代码也机会没有区别,
当手指松开后 在当前的矩形框处创建一个临时的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中会产生倒置的效果,这里要转化以下才能正常显示
学习这一章 主要学习它的思路 怎么实现在一个view上 画出一个矩形框后 就是一个testView 并且编辑完成后 在相应的位置相识出编辑的内容 当然我们也可以利用 一些其他的图文混排的库 比如DTcoreText