TextView 選中高亮動效 iOS

納蘭若水發表於2017-12-27

最近有個需求,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

 //額外新增一個用來做高亮的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];
複製程式碼

選中一段文字

    NSRange range = NSMakeRange(30, 31);
   //設定選中文字
    _maskTextview.selectedRange = range;
    UITextRange *textRange = _maskTextview.selectedTextRange;
   //獲取選中的UITextSelectionRect陣列
    NSArray *arrays =  [_maskTextview selectionRectsForRange:textRange];
複製程式碼

UITextSelectionRect

選中的UITextSelectionRect陣列分析

 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效果

- (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;
}
複製程式碼

透明效果啥的自己加吧......ƪ(˘⌣˘)┐ ƪ(˘⌣˘)ʃ ┌(˘⌣˘)ʃ 注意:

如果textview本身需要互動或者有滾動,記得同步兩個textview。這個比較複雜,這裡只是分享一個簡單case

點選下載demo檔案

後續: 發現直接在上面蓋一個textView不滿足能夠滾動的case。滾動時,覆蓋在上層的textView位置不動,監聽scroll事件會閃爍,怎麼辦呢?

1:讓覆蓋的textView一起滾動,直接把覆蓋的textView設定成另一個textview的子view 2:主textView的contentSize變化時更新覆蓋的textView的寬高為contentSize的寬高。

關鍵程式碼:

- (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地址

相關文章