这个功能用的最多的就是在微博里面,里面有用户名@南山忆,网址http://www.jianshu.com/users/774b1d5616a7/latest_articles,#话题# 等需要特别现实,还需要有点击事件,这种情况下普通的label显示已经满足不了需求了。需要我们来自己写一个可实现相应功能的label。
好了废话不多说,让我们开始,大致流程如下:

主要用到的属性
textStorage,layoutManager,textContainer的关系
1)NSTextStorage是NSMutableAttributedString的子类,由于可以灵活地往文字添加或修改属性,所以非常适用于保存并修改文字属性。
2)NSLayoutManager用来管理NSTextStorage其中的文字内容的排版布局。
3)NSTextContainer定义了一个矩形区域用于存放已经进行了排版并设置好属性的文字
三者关系如图所示

1 2 3 4 5 6
| @property (nonatomic, strong) NSTextStorage *textStorage;
@property (nonatomic, strong) NSLayoutManager *layoutManager;
@property (nonatomic, strong) NSTextContainer *textContainer;
|
需要重写系统方法,这里讲主要的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self prepareTextSystem]; } return self; }
- (void)layoutSubviews{ self.textContainer.size = self.frame.size; }
- (void)drawTextInRect:(CGRect)rect{ if (_selectedRangeValue) { UIColor *selectColor = _isSelected ? [UIColor colorWithWhite:0.6 alpha:0.2] : [UIColor clearColor]; [self.textStorage addAttribute:NSBackgroundColorAttributeName value:selectColor range:self.selectedRangeValue.rangeValue]; [self.layoutManager drawBackgroundForGlyphRange:self.selectedRangeValue.rangeValue atPoint:CGPointMake(0, 0)]; } NSRange range = NSMakeRange(0, self.textStorage.length); [self.layoutManager drawGlyphsForGlyphRange:range atPoint:CGPointZero]; }
|
准备文本显示
这里面会用到正则表达式,正则表达式是一门挺深的学问。有不太了解的童鞋可以参考
<a href=”http://deerchao.net/tutorials/regex/regex-1.htm"target="_blank">这篇文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| - (void)prepareTextSystem {
[self.textStorage addLayoutManager:self.layoutManager]; [self.layoutManager addTextContainer:self.textContainer]; self.userInteractionEnabled = YES; self.textContainer.lineFragmentPadding = 10; } - (void)prepareText{ NSAttributedString *attrString;
if (self.attributedText) { attrString = self.attributedText; }else if (self.text){ attrString = [[NSAttributedString alloc]initWithString:self.text]; }else { attrString = [[NSAttributedString alloc]initWithString:@""]; } self.selectedRangeValue = nil; NSMutableAttributedString *attrMString = [self addLineBreak:attrString]; [self.textStorage setAttributedString:attrMString]; self.linkRanges = [self getRanges:@"http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?"]; UIColor *attrColor = _attrStrColor == nil ? [UIColor blueColor] : _attrStrColor; for (NSValue *rangeValue in self.linkRanges) { [self.textStorage addAttribute:NSForegroundColorAttributeName value:attrColor range:rangeValue.rangeValue]; } self.userRanges = [self getRanges:@"@\\w{1,}?:"]; for (NSValue *rangeValue in self.userRanges) { [self.textStorage addAttribute:NSForegroundColorAttributeName value:attrColor range:rangeValue.rangeValue]; } [self setNeedsDisplay]; }
- (NSMutableAttributedString*)addLineBreak:(NSAttributedString *) attrString{ NSMutableAttributedString *attrMString = [[NSMutableAttributedString alloc]initWithAttributedString:attrString]; if (attrMString.length == 0) { return attrMString; } NSRange range = NSMakeRange(0, 0); NSMutableDictionary *dict = (NSMutableDictionary*)[attrMString attributesAtIndex:0 effectiveRange:&range]; NSLog(@"%@",dict); NSMutableParagraphStyle *paragraphStyle = [dict[NSParagraphStyleAttributeName] mutableCopy] ; if (paragraphStyle) { paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping; }else { paragraphStyle = [[NSMutableParagraphStyle alloc]init]; paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping; } [attrMString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; return attrMString; }
|
正则的匹配
1 2 3 4 5 6 7 8 9 10 11 12 13
| - (NSMutableArray<NSValue *> *)getRanges:(NSString*)pattern{ NSRegularExpression *regular = [[NSRegularExpression alloc]initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
NSArray *results = [regular matchesInString:self.textStorage.string options:NSMatchingReportCompletion range:NSMakeRange(0, self.textStorage.string.length)];
NSMutableArray *ranges = [NSMutableArray array]; for (NSTextCheckingResult *result in results) { NSValue *value = [NSValue valueWithRange:result.range]; [ranges addObject:value]; } return ranges; }
|
点击交互处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ _isSelected = YES;
CGPoint selectPoint = [[touches anyObject] locationInView:self];
self.selectedRangeValue = [self getSelectRange:selectPoint]; if (!_selectedRangeValue) { [super touchesBegan:touches withEvent:event]; } } - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ if (!_selectedRangeValue) { [super touchesEnded:touches withEvent:event]; return; }
_isSelected = NO;
[self setNeedsDisplay];
NSString *contentText = [self.textStorage.string substringWithRange:_selectedRangeValue.rangeValue]; switch (_tapHandleType) { case TapHandleUser: if (_userTaphandle) { _userTaphandle(self,contentText,_selectedRangeValue.rangeValue); } break; case TapHandlelink: if (_linkTapHandle) { _linkTapHandle(self,contentText,_selectedRangeValue.rangeValue); } break; default: break; } }
-(NSValue*)getSelectRange:(CGPoint)selectPoint{ if (self.textStorage.length == 0) { return nil; } NSInteger index = [self.layoutManager glyphIndexForPoint:selectPoint inTextContainer:self.textContainer];
for (NSValue *rangeValue in self.linkRanges) { NSRange range = rangeValue.rangeValue; if (index >= range.location && index <range.location + range.length) {
_tapHandleType = TapHandlelink; [self setNeedsDisplay];
return rangeValue; } } for (NSValue *rangeValue in self.userRanges) { NSRange range = rangeValue.rangeValue; if (index >= range.location && index <range.location + range.length) { _tapHandleType = TapHandleUser; [self setNeedsDisplay]; return rangeValue; } } _tapHandleType = TapHandleNone; return nil; }
|
总结
1、这里面主要用到了三个重要的属性:NSTextStorage、NSLayoutManager、NSTextContainer要高明白其中的关系。上面有图介绍。
2、正则表达式的使用,现在基本上大部分语言都支持正则,里面的学问很多,需要多加学习。
3、点击事件的处理。
这是<a href=”https://github.com/nanshanyi/GHLabel"target="_blank">GitHub地址可以在此查看源码。本文为原创,如需转载请注明出处。