// // TYAttributedLabel.m // TYAttributedLabelDemo // // Created by tanyang on 15/4/8. // Copyright (c) 2015年 tanyang. All rights reserved. // #import "TYAttributedLabel.h" #import #define kSelectAreaColor [UIColor colorWithRed:204/255.0 green:211/255.0 blue:236/255.0 alpha:1] #define kHighLightLinkColor [UIColor colorWithRed:28/255.0 green:0/255.0 blue:213/255.0 alpha:1] static NSString* const kEllipsesCharacter = @"\u2026"; NSString *const kTYTextRunAttributedName = @"TYTextRunAttributedName"; @interface TYTextContainer () @property (nonatomic, strong) NSMutableAttributedString *attString; @property (nonatomic, assign,readonly) CTFrameRef frameRef; - (void)resetFrameRef; - (void)resetRectDictionary; - (BOOL)existRunRectDictionary; - (BOOL)existLinkRectDictionary; - (BOOL)existDrawRectDictionary; - (void)enumerateDrawRectDictionaryUsingBlock:(void (^)(id drawStorage, CGRect rect))block; - (BOOL)enumerateRunRectContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id textStorage))successBlock; - (BOOL)enumerateLinkRectContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id linkStorage))successBlock; @end @interface TYAttributedLabel () { struct { unsigned int textStorageClickedAtPoint :1; unsigned int textStorageLongPressedOnStateAtPoint :1; }_delegateFlags; NSRange _clickLinkRange; // 点击的link的范围 } @property (nonatomic, strong) UITapGestureRecognizer *singleTapGuesture; // 点击手势 @property (nonatomic, strong) UILongPressGestureRecognizer *longPressGuesture;// 长按手势 @property (nonatomic, strong) UIColor *saveLinkColor; @end @implementation TYAttributedLabel #pragma mark - init - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setupProperty]; _textContainer = [[TYTextContainer alloc] init]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self setupProperty]; _textContainer = [[TYTextContainer alloc] init]; } return self; } - (instancetype)initWithTextContainer:(TYTextContainer *)textContainer { if (self = [super init]) { [self setupProperty]; _textContainer = textContainer; } return self; } - (void)setupProperty { if (self.backgroundColor == nil) { self.backgroundColor = [UIColor whiteColor]; } self.userInteractionEnabled = YES; _highlightedLinkColor = nil; _highlightedLinkBackgroundRadius = 2; _highlightedLinkBackgroundColor = kSelectAreaColor; } - (void)setTextContainer:(TYTextContainer *)attStringCreater { _textContainer = attStringCreater; [self resetAllAttributed]; _preferredMaxLayoutWidth = attStringCreater.textWidth; [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } - (void)setDelegate:(id)delegate { if (delegate == _delegate) return; _delegate = delegate; _delegateFlags.textStorageClickedAtPoint = [delegate respondsToSelector:@selector(attributedLabel:textStorageClicked:atPoint:)]; _delegateFlags.textStorageLongPressedOnStateAtPoint = [delegate respondsToSelector:@selector(attributedLabel:textStorageLongPressed:onState:atPoint:)]; } #pragma mark - add textStorage - (void)addTextStorage:(id)textStorage { [_textContainer addTextStorage:textStorage]; [self invalidateIntrinsicContentSize]; } - (void)addTextStorageArray:(NSArray *)textStorageArray { if (textStorageArray) { [_textContainer addTextStorageArray:textStorageArray]; [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } } - (void)resetAllAttributed { if ([NSThread isMainThread]) { [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; [self removeSingleTapGesture]; [self removeLongPressGesture]; } else { dispatch_semaphore_t signal = dispatch_semaphore_create(0); dispatch_async(dispatch_get_main_queue(), ^{ [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; [self removeSingleTapGesture]; [self removeLongPressGesture]; dispatch_semaphore_signal(signal); }); dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER); } } #pragma mark reset framesetter - (void)resetFramesetter { [_textContainer resetRectDictionary]; [_textContainer resetFrameRef]; [self setNeedsDisplay]; } #pragma mark - drawRect - (void)drawRect:(CGRect)rect { if (_textContainer == nil || _textContainer.attString == nil) { return; } [_textContainer createTextContainerWithContentSize:self.bounds.size]; // 文本垂直对齐方式位移 CGFloat verticalOffset = 0; switch (_verticalAlignment) { case TYVerticalAlignmentCenter: verticalOffset = MAX(0, (CGRectGetHeight(rect) - _textContainer.textHeight)/2); break; case TYVerticalAlignmentBottom: verticalOffset = MAX(0, (CGRectGetHeight(rect) - _textContainer.textHeight)); break; default: break; } CGFloat contextHeight = MAX(CGRectGetHeight(self.bounds) , _textContainer.textHeight); // 跟很多底层 API 一样,Core Text 使用 Y翻转坐标系统,而且内容的呈现也是上下翻转的,所以需要通过转换内容将其翻转 CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, contextHeight + verticalOffset); CGContextScaleCTM(context, 1.0, -1.0); if (_highlightedLinkBackgroundColor && [_textContainer existLinkRectDictionary]) { [self drawSelectionAreaFrame:_textContainer.frameRef InRange:_clickLinkRange radius:_highlightedLinkBackgroundRadius bgColor:_highlightedLinkBackgroundColor]; } // CTFrameDraw 将 frame 描述到设备上下文 [self drawText:_textContainer.attString frame:_textContainer.frameRef rect:rect context:context]; // 画其他元素 [self drawTextStorage]; } // this code quote M80AttributedLabel - (void)drawText: (NSAttributedString *)attributedString frame:(CTFrameRef)frame rect: (CGRect)rect context: (CGContextRef)context { if (_textContainer.numberOfLines > 0) { CFArrayRef lines = CTFrameGetLines(frame); NSInteger numberOfLines = MIN(_textContainer.numberOfLines, CFArrayGetCount(lines)); CGPoint lineOrigins[numberOfLines]; CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins); BOOL truncateLastLine = (_textContainer.lineBreakMode == kCTLineBreakByTruncatingTail); for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) { CGPoint lineOrigin = lineOrigins[lineIndex]; CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y); CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex); BOOL shouldDrawLine = YES; if (lineIndex == numberOfLines - 1 && truncateLastLine) { // Does the last line need truncation? CFRange lastLineRange = CTLineGetStringRange(line); if (lastLineRange.location + lastLineRange.length < attributedString.length) { CTLineTruncationType truncationType = kCTLineTruncationEnd; NSUInteger truncationAttributePosition = lastLineRange.location + lastLineRange.length - 1; NSDictionary *tokenAttributes = [attributedString attributesAtIndex:truncationAttributePosition effectiveRange:NULL]; NSAttributedString *tokenString = [[NSAttributedString alloc] initWithString:kEllipsesCharacter attributes:tokenAttributes]; CTLineRef truncationToken = CTLineCreateWithAttributedString((CFAttributedStringRef)tokenString); NSMutableAttributedString *truncationString = [[attributedString attributedSubstringFromRange:NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy]; if (lastLineRange.length > 0) { // Remove last token [truncationString deleteCharactersInRange:NSMakeRange(lastLineRange.length - 1, 1)]; } [truncationString appendAttributedString:tokenString]; CTLineRef truncationLine = CTLineCreateWithAttributedString((CFAttributedStringRef)truncationString); CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, rect.size.width, truncationType, truncationToken); if (!truncatedLine) { // If the line is not as wide as the truncationToken, truncatedLine is NULL truncatedLine = CFRetain(truncationToken); } CFRelease(truncationLine); CFRelease(truncationToken); CTLineDraw(truncatedLine, context); CFRelease(truncatedLine); shouldDrawLine = NO; } } if(shouldDrawLine) { CTLineDraw(line, context); } } } else { CTFrameDraw(frame,context); } } #pragma mark - drawTextStorage - (void)drawTextStorage { // draw storage [_textContainer enumerateDrawRectDictionaryUsingBlock:^(id drawStorage, CGRect rect) { if ([drawStorage conformsToProtocol:@protocol(TYViewStorageProtocol) ]) { [(id)drawStorage setOwnerView:self]; } rect = UIEdgeInsetsInsetRect(rect,drawStorage.margin); [drawStorage drawStorageWithRect:rect]; }]; if ([_textContainer existRunRectDictionary]) { if (_delegateFlags.textStorageClickedAtPoint) { [self addSingleTapGesture]; }else { [self removeSingleTapGesture]; } if (_delegateFlags.textStorageLongPressedOnStateAtPoint) { [self addLongPressGesture]; }else { [self removeLongPressGesture]; } }else { [self removeSingleTapGesture]; [self removeLongPressGesture]; } } #pragma mark - add Gesture - (void)addSingleTapGesture { if (_singleTapGuesture == nil) { // 单指单击 _singleTapGuesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)]; _singleTapGuesture.delegate = self; // 增加事件者响应者 [self addGestureRecognizer:_singleTapGuesture]; } } - (void)removeSingleTapGesture { if (_singleTapGuesture) { [self removeGestureRecognizer:_singleTapGuesture]; _singleTapGuesture = nil; } } - (void)addLongPressGesture { if (_longPressGuesture == nil) { // 长按 _longPressGuesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; [self addGestureRecognizer:_longPressGuesture]; } } - (void)removeLongPressGesture { if (_longPressGuesture) { [self removeGestureRecognizer:_longPressGuesture]; _longPressGuesture = nil; } } - (CGPoint)covertTapPiont:(CGPoint)piont { // 文本垂直对齐方式位移 CGFloat verticalOffset = 0; switch (_verticalAlignment) { case TYVerticalAlignmentCenter: verticalOffset = MAX(0, (CGRectGetHeight(self.frame) - _textContainer.textHeight)/2); break; case TYVerticalAlignmentBottom: verticalOffset = MAX(0, (CGRectGetHeight(self.frame) - _textContainer.textHeight)); break; default: break; } return CGPointMake(piont.x, piont.y-verticalOffset); } #pragma mark - Gesture action - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { CGPoint point = [touch locationInView:self]; point = [self covertTapPiont:point]; return [_textContainer enumerateRunRectContainPoint:point viewHeight:CGRectGetHeight(self.frame) successBlock:nil]; } - (void)singleTap:(UITapGestureRecognizer *)sender { CGPoint point = [sender locationInView:self]; point = [self covertTapPiont:point]; __typeof (self) __weak weakSelf = self; [_textContainer enumerateRunRectContainPoint:point viewHeight:CGRectGetHeight(self.frame) successBlock:^(id textStorage){ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf->_delegateFlags.textStorageClickedAtPoint) { [strongSelf->_delegate attributedLabel:weakSelf textStorageClicked:textStorage atPoint:point]; } }]; } - (void)longPress:(UILongPressGestureRecognizer *)sender { CGPoint point = [sender locationInView:self]; point = [self covertTapPiont:point]; __typeof (self) __weak weakSelf = self; bool didPressContainer = [_textContainer enumerateRunRectContainPoint:point viewHeight:CGRectGetHeight(self.frame) successBlock:^(id textStorage){ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf->_delegateFlags.textStorageLongPressedOnStateAtPoint) { [weakSelf.delegate attributedLabel:weakSelf textStorageLongPressed:textStorage onState:sender.state atPoint:point]; } }]; // 非响应容器区域响应长按事件 if (didPressContainer == NO && [weakSelf respondsToSelector:@selector(attributedLabel:lableLongPressOnState:atPoint:)]) { [weakSelf.delegate attributedLabel:weakSelf lableLongPressOnState:sender.state atPoint:point]; } } #pragma mark - touches action - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { __block BOOL found = NO; if ([_textContainer existLinkRectDictionary]) { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; point = [self covertTapPiont:point]; __typeof (self) __weak weakSelf = self; [_textContainer enumerateLinkRectContainPoint:point viewHeight:CGRectGetHeight(self.frame) successBlock:^(id linkStorage) { NSRange curClickLinkRange = linkStorage.realRange; [weakSelf setHighlightLinkWithSaveLinkColor:(linkStorage.textColor ? linkStorage.textColor:weakSelf.textContainer.linkColor) linkRange:curClickLinkRange]; found = YES; }]; } if (!found) { [super touchesBegan:touches withEvent:event]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; if (![_textContainer existLinkRectDictionary]) { return; } UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; point = [self covertTapPiont:point]; __block BOOL isUnderClickLink = NO; __block NSRange curClickLinkRange; __block UIColor *saveLinkColor = nil; __typeof (self) __weak weakSelf = self; [_textContainer enumerateLinkRectContainPoint:point viewHeight:CGRectGetHeight(self.frame) successBlock:^(id linkStorage) { curClickLinkRange = linkStorage.realRange; isUnderClickLink = YES; saveLinkColor = linkStorage.textColor ? linkStorage.textColor:weakSelf.textContainer.linkColor; }]; if (isUnderClickLink) { if (!NSEqualRanges(curClickLinkRange, _clickLinkRange)) { if (_saveLinkColor) { [_textContainer.attString addAttributeTextColor:_saveLinkColor range:_clickLinkRange]; } [self setHighlightLinkWithSaveLinkColor:saveLinkColor linkRange:curClickLinkRange]; } } else if(_clickLinkRange.length > 0) { [self resetHighLightLink]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; if ([_textContainer existLinkRectDictionary] && _clickLinkRange.length > 0) { [self resetHighLightLink]; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; if ([_textContainer existLinkRectDictionary] && _clickLinkRange.length > 0) { [self resetHighLightLink]; } } // 设置高亮链接 - (void)setHighlightLinkWithSaveLinkColor:(UIColor *)saveLinkColor linkRange:(NSRange)linkRange { if (NSMaxRange(linkRange) > _textContainer.attString.length) { _clickLinkRange.length = 0; return; } _clickLinkRange = linkRange; if (_highlightedLinkColor) { [_textContainer.attString addAttributeTextColor:_highlightedLinkColor range:_clickLinkRange]; _saveLinkColor = saveLinkColor; [self resetFramesetter]; }else{ [self setNeedsDisplay]; } } // 取消高亮 - (void)resetHighLightLink { if (_highlightedLinkColor) { if (_saveLinkColor) { [_textContainer.attString addAttributeTextColor:_saveLinkColor range:_clickLinkRange]; _saveLinkColor = nil; } _clickLinkRange.length = 0; [self resetFramesetter]; }else { _clickLinkRange.length = 0; [self setNeedsDisplay]; } } #pragma mark - draw Rect // 绘画选择区域 - (void)drawSelectionAreaFrame:(CTFrameRef)frameRef InRange:(NSRange)selectRange radius:(CGFloat)radius bgColor:(UIColor *)bgColor{ NSInteger selectionStartPosition = selectRange.location; NSInteger selectionEndPosition = NSMaxRange(selectRange); if (selectionStartPosition < 0 || selectRange.length <= 0 || selectionEndPosition > _textContainer.attString.length) { return; } CFArrayRef lines = CTFrameGetLines(frameRef); if (!lines) { return; } CFIndex count = CFArrayGetCount(lines); // 获得每一行的origin坐标 CGPoint origins[count]; CTFrameGetLineOrigins(frameRef, CFRangeMake(0,0), origins); for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFRange range = CTLineGetStringRange(line); // 1. start和end在一个line,则直接弄完break if ([self isPosition:selectionStartPosition inRange:range] && [self isPosition:selectionEndPosition inRange:range]) { CGFloat ascent, descent, leading, offset, offset2; offset = CTLineGetOffsetForStringIndex(line, selectionStartPosition, NULL); offset2 = CTLineGetOffsetForStringIndex(line, selectionEndPosition, NULL); CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x + offset, linePoint.y - descent, offset2 - offset, ascent + descent); [self fillSelectionAreaInRect:lineRect radius:radius bgColor:bgColor]; break; } // 2. start和end不在一个line // 2.1 如果start在line中,则填充Start后面部分区域 if ([self isPosition:selectionStartPosition inRange:range]) { CGFloat ascent, descent, leading, width, offset; offset = CTLineGetOffsetForStringIndex(line, selectionStartPosition, NULL); width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x + offset, linePoint.y - descent, width - offset, ascent + descent); [self fillSelectionAreaInRect:lineRect radius:radius bgColor:bgColor]; } // 2.2 如果 start在line前,end在line后,则填充整个区域 else if (selectionStartPosition < range.location && selectionEndPosition >= range.location + range.length) { CGFloat ascent, descent, leading, width; width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x, linePoint.y - descent, width, ascent + descent); [self fillSelectionAreaInRect:lineRect radius:radius bgColor:bgColor]; } // 2.3 如果start在line前,end在line中,则填充end前面的区域,break else if (selectionStartPosition < range.location && [self isPosition:selectionEndPosition inRange:range]) { CGFloat offset = CTLineGetOffsetForStringIndex(line, selectionEndPosition, NULL); CGRect lineRect = CGRectMake(linePoint.x, linePoint.y, offset, 0); [self fillSelectionAreaInRect:lineRect radius:radius bgColor:bgColor]; } } } - (BOOL)isPosition:(NSInteger)position inRange:(CFRange)range { return (position >= range.location && position < range.location + range.length); } - (void)fillSelectionAreaInRect:(CGRect)rect radius:(CGFloat)radius bgColor:(UIColor *)bgColor { CGFloat x = rect.origin.x; CGFloat y = rect.origin.y; CGFloat width = rect.size.width; CGFloat height = rect.size.height; // 获取CGContext CGContextRef context = UIGraphicsGetCurrentContext(); // 移动到初始点 CGContextMoveToPoint(context, x + radius, y); // 绘制第1条线和第1个1/4圆弧 CGContextAddLineToPoint(context, x + width - radius, y); CGContextAddArc(context,x+ width - radius,y+ radius, radius, -0.5 * M_PI, 0.0, 0); // 绘制第2条线和第2个1/4圆弧 CGContextAddLineToPoint(context, x + width,y + height - radius); CGContextAddArc(context,x+ width - radius,y+ height - radius, radius, 0.0, 0.5 * M_PI, 0); // 绘制第3条线和第3个1/4圆弧 CGContextAddLineToPoint(context, x+radius, y+height); CGContextAddArc(context, x+radius,y+ height - radius, radius, 0.5 * M_PI, M_PI, 0); // 绘制第4条线和第4个1/4圆弧 CGContextAddLineToPoint(context, x,y+ radius); CGContextAddArc(context,x+ radius,y+ radius, radius, M_PI, 1.5 * M_PI, 0); // 闭合路径 CGContextClosePath(context); // 填充颜色 CGContextSetFillColorWithColor(context, bgColor.CGColor); CGContextDrawPath(context, kCGPathFill); // CGContextRef context = UIGraphicsGetCurrentContext(); // CGContextSetFillColorWithColor(context, bgColor.CGColor); // CGContextFillRect(context, rect); } #pragma mark - get Right Height - (int)getHeightWithWidth:(CGFloat)width { // 是否需要更新frame return [_textContainer getHeightWithFramesetter:nil width:width]; } - (CGSize)getSizeWithWidth:(CGFloat)width { return [_textContainer getSuggestedSizeWithFramesetter:nil width:width]; } - (void)sizeToFit { [super sizeToFit]; } - (CGSize)sizeThatFits:(CGSize)size { return [self getSizeWithWidth:CGRectGetWidth(self.frame)]; } - (void)setPreferredMaxLayoutWidth:(CGFloat)preferredMaxLayoutWidth { if (_preferredMaxLayoutWidth != preferredMaxLayoutWidth) { _preferredMaxLayoutWidth = preferredMaxLayoutWidth; [self invalidateIntrinsicContentSize]; } } - (CGSize)intrinsicContentSize { return [self getSizeWithWidth:_preferredMaxLayoutWidth]; } #pragma mark - set right frame - (void)setFrameWithOrign:(CGPoint)orign Width:(CGFloat)width { // 获得高度 int height = [self getHeightWithWidth:width]; // 设置frame [self setFrame:CGRectMake(orign.x, orign.y, width, height)]; } - (void)dealloc { _textContainer = nil; } #pragma mark - getter - (NSString *)text{ return _textContainer.text; } - (NSAttributedString *)attributedText { return _textContainer.attributedText; } - (NSInteger)numberOfLines { return _textContainer.numberOfLines; } - (UIColor *)textColor { return _textContainer.textColor; } - (UIFont *)font { return _textContainer.font; } - (UIColor *)strokeColor { return _textContainer.strokeColor; } - (unichar)strokeWidth { return _textContainer.strokeWidth; } - (unichar)characterSpacing { return _textContainer.characterSpacing; } - (CGFloat)linesSpacing { return _textContainer.linesSpacing; } - (CGFloat)paragraphSpacing { return _textContainer.paragraphSpacing; } - (CTLineBreakMode)lineBreakMode { return _textContainer.lineBreakMode; } - (CTTextAlignment)textAlignment { return _textContainer.textAlignment; } - (CGFloat)textHeight{ return _textContainer.textHeight; } - (UIColor *)linkColor { return _textContainer.linkColor; } - (BOOL)isWidthToFit { return _textContainer.isWidthToFit; } #pragma mark - setter - (void)setText:(NSString *)text { [_textContainer setText:text]; [self resetAllAttributed]; if ([NSThread isMainThread]) { [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } else { dispatch_semaphore_t signal = dispatch_semaphore_create(0); dispatch_async(dispatch_get_main_queue(), ^{ [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; dispatch_semaphore_signal(signal); }); dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER); } } - (void)setAttributedText:(NSAttributedString *)attributedText { [_textContainer setAttributedText:attributedText]; [self resetAllAttributed]; if ([NSThread isMainThread]) { [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } else { dispatch_semaphore_t signal = dispatch_semaphore_create(0); dispatch_async(dispatch_get_main_queue(), ^{ [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; dispatch_semaphore_signal(signal); }); dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER); } } - (void)setNumberOfLines:(NSInteger)numberOfLines { [_textContainer setNumberOfLines:numberOfLines]; } - (void)setTextColor:(UIColor *)textColor { [_textContainer setTextColor:textColor]; [self setNeedsDisplay]; } - (void)setFont:(UIFont *)font { [_textContainer setFont:font]; if ([NSThread isMainThread]) { [self setNeedsDisplay]; } else { dispatch_semaphore_t signal = dispatch_semaphore_create(0); dispatch_async(dispatch_get_main_queue(), ^{ [self setNeedsDisplay]; dispatch_semaphore_signal(signal); }); dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER); } } - (void)setStrokeWidth:(unichar)strokeWidth { [_textContainer setStrokeWidth:strokeWidth]; [self setNeedsDisplay]; } - (void)setStrokeColor:(UIColor *)strokeColor { [_textContainer setStrokeColor:strokeColor]; [self setNeedsDisplay]; } - (void)setCharacterSpacing:(unichar)characterSpacing { [_textContainer setCharacterSpacing:characterSpacing]; [self setNeedsDisplay]; } - (void)setLinesSpacing:(CGFloat)linesSpacing { [_textContainer setLinesSpacing:linesSpacing]; [self setNeedsDisplay]; } - (void)setParagraphSpacing:(CGFloat)paragraphSpacing { [_textContainer setParagraphSpacing:paragraphSpacing]; [self setNeedsDisplay]; } - (void)setLineBreakMode:(CTLineBreakMode)lineBreakMode { [_textContainer setLineBreakMode:lineBreakMode]; [self setNeedsDisplay]; } - (void)setTextAlignment:(CTTextAlignment)textAlignment { [_textContainer setTextAlignment:textAlignment]; [self setNeedsDisplay]; } - (void)setLinkColor:(UIColor *)linkColor { [_textContainer setLinkColor:linkColor]; } - (void)setIsWidthToFit:(BOOL)isWidthToFit { [_textContainer setIsWidthToFit:isWidthToFit]; } @end #pragma mark - append attributedString @implementation TYAttributedLabel (AppendAttributedString) - (void)appendText:(NSString *)text { [_textContainer appendText:text]; [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } - (void)appendTextAttributedString:(NSAttributedString *)attributedText { [_textContainer appendTextAttributedString:attributedText]; [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } - (void)appendTextStorage:(id)textStorage { if (textStorage) { [_textContainer appendTextStorage:textStorage]; [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } } - (void)appendTextStorageArray:(NSArray *)textStorageArray { if (textStorageArray) { [_textContainer appendTextStorageArray:textStorageArray]; [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } } @end @implementation TYAttributedLabel (Link) #pragma mark - addLink - (void)addLinkWithLinkData:(id)linkData range:(NSRange)range { [self addLinkWithLinkData:linkData linkColor:nil range:range]; } - (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor range:(NSRange )range; { [self addLinkWithLinkData:linkData linkColor:linkColor underLineStyle:kCTUnderlineStyleSingle range:range]; } - (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle range:(NSRange )range { [_textContainer addLinkWithLinkData:linkData linkColor:linkColor underLineStyle:underLineStyle range:range]; [self setNeedsDisplay]; } #pragma mark - appendLink - (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkData:(id)linkData { [self appendLinkWithText:linkText linkFont:linkFont linkColor:nil linkData:linkData]; } - (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor linkData:(id)linkData { [self appendLinkWithText:linkText linkFont:linkFont linkColor:linkColor underLineStyle:kCTUnderlineStyleSingle linkData:linkData]; } - (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle linkData:(id)linkData { [_textContainer appendLinkWithText:linkText linkFont:linkFont linkColor:linkColor underLineStyle:underLineStyle linkData:linkData]; [self setNeedsDisplay]; } @end @implementation TYAttributedLabel (UIImage) #pragma mark addImage - (void)addImage:(UIImage *)image range:(NSRange)range size:(CGSize)size alignment:(TYDrawAlignment)alignment { [_textContainer addImage:image range:range size:size alignment:alignment]; } - (void)addImage:(UIImage *)image range:(NSRange)range size:(CGSize)size { [self addImage:image range:range size:size alignment:TYDrawAlignmentTop]; } - (void)addImage:(UIImage *)image range:(NSRange)range { [self addImage:image range:range size:image.size]; } - (void)addImageWithName:(NSString *)imageName range:(NSRange)range size:(CGSize)size alignment:(TYDrawAlignment)alignment { [_textContainer addImageWithName:imageName range:range size:size alignment:alignment]; } - (void)addImageWithName:(NSString *)imageName range:(NSRange)range size:(CGSize)size { [self addImageWithName:imageName range:range size:size alignment:TYDrawAlignmentTop]; } - (void)addImageWithName:(NSString *)imageName range:(NSRange)range { [self addImageWithName:imageName range:range size:CGSizeMake(self.font.pointSize, self.font.ascender)]; } #pragma mark - appendImage - (void)appendImage:(UIImage *)image size:(CGSize)size alignment:(TYDrawAlignment)alignment { [_textContainer appendImage:image size:size alignment:alignment]; } - (void)appendImage:(UIImage *)image size:(CGSize)size { [self appendImage:image size:size alignment:TYDrawAlignmentTop]; } - (void)appendImage:(UIImage *)image { [self appendImage:image size:image.size]; } - (void)appendImageWithName:(NSString *)imageName size:(CGSize)size alignment:(TYDrawAlignment)alignment { [_textContainer appendImageWithName:imageName size:size alignment:alignment]; } - (void)appendImageWithName:(NSString *)imageName size:(CGSize)size { [self appendImageWithName:imageName size:size alignment:TYDrawAlignmentTop]; } - (void)appendImageWithName:(NSString *)imageName { [self appendImageWithName:imageName size:CGSizeMake(self.font.pointSize, self.font.ascender)]; } @end @implementation TYAttributedLabel (UIView) #pragma mark - addView - (void)addView:(UIView *)view range:(NSRange)range alignment:(TYDrawAlignment)alignment { [_textContainer addView:view range:range alignment:alignment]; } - (void)addView:(UIView *)view range:(NSRange)range { [self addView:view range:range alignment:TYDrawAlignmentTop]; } #pragma mark - appendView - (void)appendView:(UIView *)view alignment:(TYDrawAlignment)alignment { [_textContainer appendView:view alignment:alignment]; } - (void)appendView:(UIView *)view { [self appendView:view alignment:TYDrawAlignmentTop]; } @end