TextView 选中高亮动效---iOS

最近有个需求,TextView显示从录音转出的文字,在播放时,播放一段文字时需要高亮选中动效(刷色+透明度变换)。
解决步骤:

1:覆盖一个一模一样的textView、相同的frame、font。
2:设置textView的selectedRange
3:拿到textView的selectedTextRange
4:根据selectedTextRange通过selectionRectsForRange方法拿到选中的UITextSelectionRect数组,UITextSelectionRect中包含选中的rect信息,选中的首尾位置。
5:分析UITextSelectionRect数组给textView的layer添加CABasicAnimation的相关path和opacity等动效

代码很简单:
新建一个同样的textView

1
2
3
4
5
6
7
8
9
10
11
12
13
//额外添加一个用来做高亮的textview
_maskTextview = [UITextView new];
_maskTextview.text = testString;
_maskTextview.textColor = [UIColor redColor];//你要高亮的颜色
_maskTextview.font = [UIFont systemFontOfSize:20];
_maskTextview.frame = textView.frame;

[self.view addSubview:_maskTextview];

_layer = [CAShapeLayer layer];
_layer.frame = _maskTextview.bounds;
_layer.fillColor = [UIColor blackColor].CGColor;//本来textview的颜色
[_maskTextview.layer setMask:_layer];


选中一段文字
1
2
3
4
5
6
 NSRange range = NSMakeRange(30, 31);
//设置选中文字
_maskTextview.selectedRange = range;
UITextRange *textRange = _maskTextview.selectedTextRange;
//获取选中的UITextSelectionRect数组
NSArray *arrays = [_maskTextview selectionRectsForRange:textRange];

选中的UITextSelectionRect数组分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for (UITextSelectionRect *rect in arrays) {
//选中的rect 项可能包含多行,每一行做动效的可以通过containsStart 和 containsEnd 的height定位每行的位置
NSLog(@"rectX is :%f",rect.rect.origin.x);
NSLog(@"rectY is :%f",rect.rect.origin.y);
NSLog(@"rectW is :%f",rect.rect.size.width);
NSLog(@"rectH is :%f",rect.rect.size.height);

NSLog(@"rect.containsStart is :%hhd",rect.containsStart);
NSLog(@"rect.containsEnd is :%hhd",rect.containsEnd);
NSLog(@"rect.isVertical is :%hhd",rect.isVertical);
//NSLog(@"rect.writingDirection is :%d",rect.writingDirection);
if(rect.containsEnd || rect.containsStart) {
continue;
}
int line = rect.rect.size.height / lineHeight;
for (int i = 0; i < line; i++)
{
CGFloat y = rect.rect.origin.y + (rect.rect.size.height/line) * i;
CGRect layerRect = CGRectMake(rect.rect.origin.x, y, rect.rect.size.width, rect.rect.size.height/line);
[self addSelectedLayerWithRect:layerRect];
}
}

添加一个path效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)addSelectedLayerWithRect:(CGRect)rect
{
//添加一个上下的高亮效果的layer
//如果是左右的动效的则需要把初始位置和最后的位置取出进行排序后再添加
CAShapeLayer *layer = [CAShapeLayer layer];
layer.frame = _maskTextview.bounds;
layer.fillColor = [UIColor blackColor].CGColor;
[_layer addSublayer:layer];

UIBezierPath *fromPath = [UIBezierPath bezierPathWithRect:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 0)];
UIBezierPath *toPath = [UIBezierPath bezierPathWithRect:rect];
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
basicAnimation.duration = 5;
basicAnimation.repeatCount = 1;
basicAnimation.fromValue = (__bridge id)fromPath.CGPath;
basicAnimation.toValue = (__bridge id)toPath.CGPath;
[layer addAnimation:basicAnimation forKey:@"path"];
layer.path = toPath.CGPath;
}

透明效果啥的自己加吧……ƪ(˘⌣˘)┐ ƪ(˘⌣˘)ʃ ┌(˘⌣˘)ʃ

点击下载demo文件

后续:
发现直接在上面盖一个textView不满足能够滚动的case。滚动时,覆盖在上层的textView位置不动,监听scroll事件会闪烁,怎么办呢?

1:让覆盖的textView一起滚动,直接把覆盖的textView设置成另一个textview的子view
2:主textView的contentSize变化时更新覆盖的textView的宽高为contentSize的宽高。

关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)commonInit {
self.contentSize = CGSizeZero;
self.userInteractionEnabled = YES;
self.textColor = kLayerFillColor;
_maskTextView = [UITextView new];
_maskTextView.textColor = kHighlightColor;
_maskTextView.userInteractionEnabled = NO;
_maskTextView.backgroundColor = [UIColor clearColor];
[self addSubview:_maskTextView];
_highlightLayer = [CAShapeLayer layer];
_highlightLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
_highlightLayer.fillColor = kLayerFillColor.CGColor;//本来textview的颜色
[_maskTextView.layer setMask:_highlightLayer];
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
_maskTextView.frame = CGRectMake(0, 0, contentSize.width, contentSize.height);
_highlightLayer.frame = CGRectMake(0, 0, contentSize.width, contentSize.height);
}

github demo地址