0.TextKit包含类讲解
如图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显示
主角: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 文字内不规则的区域留白
主角: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实现链接点击的效果
主角:NSLayoutManager####
上述为点击高亮前后的显示
两副场景如果是"单独"显示,用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