0.TextKit包含類講解
如圖TextKit_1可以看到,我們一般能接觸到的文字控件全是由TextKit封裝而成的孵构。
TextKit內(nèi)的三個類功能如下:
NSTextStorage:保存文字控件要顯示的NSAttributedString
NSLayoutManager:掌控文字控件對文字的具體繪制操作
NSTextContainer:把控文字控件的顯示的區(qū)域
隸屬關(guān)系如下:
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.開發(fā)時的應用
TextKitTrain源碼
1.1 同一段文字分View顯示
主角:NSTextStorage####
如圖TextKit_2可以看到办成,NSTextStorage穿肄、NSLayoutManager 和 NSTextContainer 之間的箭頭都是有兩個頭的荷科。我試圖描述它們的關(guān)系是 1 對 N 的關(guā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 文字內(nèi)不規(guī)則的區(qū)域留白
主角: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,很簡單的就在正常的顯示區(qū)域內(nèi)剪出個"洞"袱吆。
1.3 Label實現(xiàn)鏈接點擊的效果
主角:NSLayoutManager####
上述為點擊高亮前后的顯示
兩副場景如果是"單獨"顯示,用attributedString+UILabel應該誰都會,難點就在兩副場景的瞬間切換,切換不同的attributedString
- step1.預先標定高亮區(qū)域
- step2.點擊時識別是否在高亮區(qū)域
- step3.在高亮區(qū)域,切換Label的attributedString
1.3.1 預先標定高亮區(qū)域
UI層面預先標定:
特殊區(qū)域文字顏色與普通文字顏色不同,通過[attributedString addAttribute:NSForegroundColorAttributeName value:range:]
的方式來實現(xiàn)
數(shù)據(jù)層面預先標定:
@property (nonatomic, copy) NSMutableArray * selectableRanges;
label設置selectableRanges屬性用于保存所有特殊區(qū)域的range
1.3.2 點擊時識別是否在高亮區(qū)域
NSUInteger touchedChar = [self.textLayoutManager glyphIndexForPoint:location inTextContainer:self.textContainer];
獲取"點擊點"所點中字符的index,然后遍歷selectableRanges內(nèi)的每一個range,看是否有哪個range包含了這個index
- 沒有一個range包含那就罷了
- 有一個range包含就進行下一步
1.3.3 在高亮區(qū)域,切換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