iOS Text Part1:TextKit

作者: 破弓 | 来源:发表于2016-07-21 21:25 被阅读423次

    0.TextKit包含类讲解

    TextKit_1.png
    TextKit_2.png

    如图TextKit_1可以看到,我们一般能接触到的文字控件全是由TextKit封装而成的。

    TextKit内的三个类功能如下:
    NSTextStorage:保存文字控件要显示的NSAttributedString
    NSLayoutManager:掌控文字控件对文字的具体绘制操作
    NSTextContainer:把控文字控件的显示的区域
    

    隶属关系如下:

     self.textStorage = [[NSTextStorage alloc] init];
     self.textLayoutManager = [[NSLayoutManager alloc] init];
     self.textContainer = [[NSTextContainer alloc]init];
        
     [self.textStorage addLayoutManager:self.textLayoutManager];
     [self.textLayoutManager addTextContainer:self.textContainer];
        
     self.textLayoutManager.delegate = self;
    

    1.开发时的应用

    TextKitTrain源码

    1.1 同一段文字分View显示

    Simulator Screen Shot 2016年7月20日 下午7.54.28.png

    主角:NSTextStorage####

    如图TextKit_2可以看到,NSTextStorage、NSLayoutManager 和 NSTextContainer 之间的箭头都是有两个头的。我试图描述它们的关系是 1 对 N 的关系。就是那样:一个 Text Storage 可以拥有多个 Layout Manager,一个 Layout Manager 也可以拥有多个 Text Container。这些多重性带来了很好的特性。
    很有用的一个例子,基于页面的布局:
    页面上有多个 Text View ,每个Text View 的 Text Container 都引用同一个 Layout Manager,一个 Text Storage持有这个 Layout Manager,这时这个 Text Storage 就可以将文本分布到多个视图上来显示。

    1.2 文字内不规则的区域留白

    Simulator Screen Shot 2016年7月20日 下午7.54.05.png

    主角:NSTextContainer####

     CGRect ovalFrame = [_textView convertRect:_panView.bounds fromView:_panView];
     // Simply set the exclusion path
     UIBezierPath * ovalPath = [UIBezierPath bezierPathWithOvalInRect:ovalFrame];
     _textView.textContainer.exclusionPaths = @[ovalPath];
     [_textView setNeedsDisplay];
    

    拿到混入图片的矩形坐标,由矩形坐标获取画圆的贝赛尔曲线,将这个贝赛尔曲线赋值给textContainer.exclusionPaths,很简单的就在正常的显示区域内剪出个"洞"。

    1.3 Label实现链接点击的效果

    Simulator Screen Shot 2016年7月20日 下午8.31.28.png

    主角:NSLayoutManager####

    before.png
    select ing.png

    上述为点击高亮前后的显示
    两副场景如果是"单独"显示,用attributedString+UILabel应该谁都会,难点就在两副场景的瞬间切换,切换不同的attributedString

    • step1.预先标定高亮区域
    • step2.点击时识别是否在高亮区域
    • step3.在高亮区域,切换Label的attributedString

    1.3.1 预先标定高亮区域

    UI层面预先标定:
    特殊区域文字颜色与普通文字颜色不同,通过[attributedString addAttribute:NSForegroundColorAttributeName value:range:]的方式来实现

    数据层面预先标定:
    @property (nonatomic, copy) NSMutableArray * selectableRanges;
    label设置selectableRanges属性用于保存所有特殊区域的range

    1.3.2 点击时识别是否在高亮区域

     NSUInteger touchedChar = [self.textLayoutManager glyphIndexForPoint:location inTextContainer:self.textContainer];
    

    获取"点击点"所点中字符的index,然后遍历selectableRanges内的每一个range,看是否有哪个range包含了这个index

    • 没有一个range包含那就罢了
    • 有一个range包含就进行下一步

    1.3.3 在高亮区域,切换Label的attributedString

    -(void)updateShowTextWithRange:(NSRange)range color:(UIColor *)color
    {
        NSMutableAttributedString * muAttr = [self.attributedText mutableCopy];
        if (range.length <= 0) {
            [muAttr removeAttribute:NSBackgroundColorAttributeName range:self.selectRange];
        }else{
            [muAttr addAttribute:NSBackgroundColorAttributeName value:color range:range];
        }
        self.attributedText = [muAttr copy];
        self.selectRange = range;
    }
    

    通过[attributedString addAttribute: NSBackgroundColorAttributeName value:range:]的方式更新点中range背景色,然后重新绘制

    - (void)drawTextInRect:(CGRect)rect
    {
        CGPoint textOffset;
        NSRange glyphRange = [self.textLayoutManager glyphRangeForTextContainer:self.textContainer];
        textOffset = [self calcTextOffsetForGlyphRange:glyphRange];
        //绘制背景
        [self.textLayoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textOffset];
        //绘制字符
        [self.textLayoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textOffset];
    }
    

    学习来源:
    http://objccn.io/issue-5-1/
    https://github.com/m1entus/MZSelectableLabel
    

    相关文章

      网友评论

        本文标题:iOS Text Part1:TextKit

        本文链接:https://www.haomeiwen.com/subject/xersjttx.html