1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150 |
- //
- // M80AttributedLabel.m
- // M80AttributedLabel
- //
- // Created by amao on 13-9-1.
- // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved.
- //
- #import "M80AttributedLabel.h"
- #import "M80AttributedLabelAttachment.h"
- #import "M80AttributedLabelURL.h"
- static NSString* const M80EllipsesCharacter = @"\u2026";
- static dispatch_queue_t m80_attributed_label_parse_queue;
- static dispatch_queue_t get_m80_attributed_label_parse_queue() \
- {
- if (m80_attributed_label_parse_queue == NULL) {
- m80_attributed_label_parse_queue = dispatch_queue_create("com.m80.parse_queue", 0);
- }
- return m80_attributed_label_parse_queue;
- }
- @interface M80AttributedLabel ()
- {
- NSMutableArray *_attachments;
- NSMutableArray *_linkLocations;
- CTFrameRef _textFrame;
- CGFloat _fontAscent;
- CGFloat _fontDescent;
- CGFloat _fontHeight;
- }
- @property (nonatomic,strong) NSMutableAttributedString *attributedString;
- @property (nonatomic,strong) M80AttributedLabelURL *touchedLink;
- @property (nonatomic,assign) BOOL linkDetected;
- @property (nonatomic,assign) BOOL ignoreRedraw;
- @end
- @implementation M80AttributedLabel
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self)
- {
- [self commonInit];
- }
- return self;
- }
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- self = [super initWithCoder:aDecoder];
- if (self)
- {
- [self commonInit];
- }
- return self;
- }
- - (void)dealloc
- {
- if (_textFrame)
- {
- CFRelease(_textFrame);
- }
-
- }
- #pragma mark - 初始化
- - (void)commonInit
- {
- _attributedString = [[NSMutableAttributedString alloc]init];
- _attachments = [[NSMutableArray alloc]init];
- _linkLocations = [[NSMutableArray alloc]init];
- _textFrame = nil;
- _linkColor = [UIColor blueColor];
- _font = [UIFont systemFontOfSize:15];
- _textColor = [UIColor blackColor];
- _highlightColor = [UIColor colorWithRed:0xd7/255.0
- green:0xf2/255.0
- blue:0xff/255.0
- alpha:1];
- _lineBreakMode = kCTLineBreakByWordWrapping;
- _underLineForLink = YES;
- _autoDetectLinks = YES;
- _lineSpacing = 0.0;
- _paragraphSpacing = 0.0;
- if (self.backgroundColor == nil)
- {
- self.backgroundColor = [UIColor whiteColor];
- }
-
- self.userInteractionEnabled = YES;
- [self resetFont];
- }
- - (void)cleanAll
- {
- _ignoreRedraw = NO;
- _linkDetected = NO;
- [_attachments removeAllObjects];
- [_linkLocations removeAllObjects];
- self.touchedLink = nil;
- for (UIView *subView in self.subviews)
- {
- [subView removeFromSuperview];
- }
- [self resetTextFrame];
- }
- - (void)resetTextFrame
- {
- if (_textFrame)
- {
- CFRelease(_textFrame);
- _textFrame = nil;
- }
- if ([NSThread isMainThread] && !_ignoreRedraw)
- {
- [self setNeedsDisplay];
- }
- }
- - (void)resetFont
- {
- CTFontRef fontRef = CTFontCreateWithName((CFStringRef)self.font.fontName, self.font.pointSize, NULL);
- if (fontRef)
- {
- _fontAscent = CTFontGetAscent(fontRef);
- _fontDescent = CTFontGetDescent(fontRef);
- _fontHeight = CTFontGetSize(fontRef);
- CFRelease(fontRef);
- }
- }
- #pragma mark - 属性设置
- //保证正常绘制,如果传入nil就直接不处理
- - (void)setFont:(UIFont *)font
- {
- if (font && _font != font)
- {
- _font = font;
-
- [_attributedString m80_setFont:_font];
- [self resetFont];
- for (M80AttributedLabelAttachment *attachment in _attachments)
- {
- attachment.fontAscent = _fontAscent;
- attachment.fontDescent = _fontDescent;
- }
- [self resetTextFrame];
- }
- }
- - (void)setTextColor:(UIColor *)textColor
- {
- if (textColor && _textColor != textColor)
- {
- _textColor = textColor;
- [_attributedString m80_setTextColor:textColor];
- [self resetTextFrame];
- }
- }
- - (void)setHighlightColor:(UIColor *)highlightColor
- {
- if (highlightColor && _highlightColor != highlightColor)
- {
- _highlightColor = highlightColor;
-
- [self resetTextFrame];
- }
- }
- - (void)setLinkColor:(UIColor *)linkColor
- {
- if (_linkColor != linkColor)
- {
- _linkColor = linkColor;
- [self resetTextFrame];
- }
- }
- - (void)setFrame:(CGRect)frame
- {
- CGRect oldRect = self.bounds;
- [super setFrame:frame];
-
- if (!CGRectEqualToRect(self.bounds, oldRect))
- {
- [self resetTextFrame];
- }
- }
- - (void)setBounds:(CGRect)bounds
- {
- CGRect oldRect = self.bounds;
- [super setBounds:bounds];
-
- if (!CGRectEqualToRect(self.bounds, oldRect))
- {
- [self resetTextFrame];
- }
- }
- - (void)setShadowColor:(UIColor *)shadowColor
- {
- if (_shadowColor != shadowColor)
- {
- _shadowColor = shadowColor;
- [self resetTextFrame];
- }
- }
- - (void)setShadowOffset:(CGSize)shadowOffset
- {
- if (!CGSizeEqualToSize(_shadowOffset, shadowOffset))
- {
- _shadowOffset = shadowOffset;
- [self resetTextFrame];
- }
- }
- - (void)setShadowBlur:(CGFloat)shadowBlur
- {
- if (_shadowBlur != shadowBlur)
- {
- _shadowBlur = shadowBlur;
- [self resetTextFrame];
- }
- }
- #pragma mark - 辅助方法
- - (NSAttributedString *)attributedString:(NSString *)text
- {
- if ([text length])
- {
- NSMutableAttributedString *string = [[NSMutableAttributedString alloc]initWithString:text];
- [string m80_setFont:self.font];
- [string m80_setTextColor:self.textColor];
- return string;
- }
- else
- {
- return [[NSAttributedString alloc] init];
- }
- }
- - (NSInteger)numberOfDisplayedLines
- {
- CFArrayRef lines = CTFrameGetLines(_textFrame);
- return _numberOfLines > 0 ? MIN(CFArrayGetCount(lines), _numberOfLines) : CFArrayGetCount(lines);
- }
- - (NSAttributedString *)attributedStringForDraw
- {
- if (_attributedString)
- {
- //添加排版格式
- NSMutableAttributedString *drawString = [_attributedString mutableCopy];
-
- //如果LineBreakMode为TranncateTail,那么默认排版模式改成kCTLineBreakByCharWrapping,使得尽可能地显示所有文字
- CTLineBreakMode lineBreakMode = self.lineBreakMode;
- if (self.lineBreakMode == kCTLineBreakByTruncatingTail)
- {
- lineBreakMode = _numberOfLines == 1 ? kCTLineBreakByTruncatingTail : kCTLineBreakByWordWrapping;
- }
- CGFloat fontLineHeight = self.font.lineHeight; //使用全局fontHeight作为最小lineHeight
-
-
- CTParagraphStyleSetting settings[] =
- {
- {kCTParagraphStyleSpecifierAlignment,sizeof(_textAlignment),&_textAlignment},
- {kCTParagraphStyleSpecifierLineBreakMode,sizeof(lineBreakMode),&lineBreakMode},
- {kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(_lineSpacing),&_lineSpacing},
- {kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(_lineSpacing),&_lineSpacing},
- {kCTParagraphStyleSpecifierParagraphSpacing,sizeof(_paragraphSpacing),&_paragraphSpacing},
- {kCTParagraphStyleSpecifierMinimumLineHeight,sizeof(fontLineHeight),&fontLineHeight},
- };
- CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings,sizeof(settings) / sizeof(settings[0]));
- [drawString addAttribute:(id)kCTParagraphStyleAttributeName
- value:(__bridge id)paragraphStyle
- range:NSMakeRange(0, [drawString length])];
- CFRelease(paragraphStyle);
-
-
- for (M80AttributedLabelURL *url in _linkLocations)
- {
- if (url.range.location + url.range.length >[_attributedString length])
- {
- continue;
- }
- UIColor *drawLinkColor = url.color ? : self.linkColor;
- [drawString m80_setTextColor:drawLinkColor range:url.range];
- [drawString m80_setUnderlineStyle:_underLineForLink ? kCTUnderlineStyleSingle : kCTUnderlineStyleNone
- modifier:kCTUnderlinePatternSolid
- range:url.range];
- }
- return drawString;
- }
- else
- {
- return nil;
- }
- }
- - (M80AttributedLabelURL *)urlForPoint:(CGPoint)point
- {
- static const CGFloat kVMargin = 5;
- if (!CGRectContainsPoint(CGRectInset(self.bounds, 0, -kVMargin), point)
- || _textFrame == nil)
- {
- return nil;
- }
-
- CFArrayRef lines = CTFrameGetLines(_textFrame);
- if (!lines)
- return nil;
- CFIndex count = CFArrayGetCount(lines);
-
- CGPoint origins[count];
- CTFrameGetLineOrigins(_textFrame, CFRangeMake(0,0), origins);
-
- CGAffineTransform transform = [self transformForCoreText];
- CGFloat verticalOffset = 0; //不像Nimbus一样设置文字的对齐方式,都统一是TOP,那么offset就为0
-
- for (int i = 0; i < count; i++)
- {
- CGPoint linePoint = origins[i];
-
- CTLineRef line = CFArrayGetValueAtIndex(lines, i);
- CGRect flippedRect = [self getLineBounds:line point:linePoint];
- CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
-
- rect = CGRectInset(rect, 0, -kVMargin);
- rect = CGRectOffset(rect, 0, verticalOffset);
-
- if (CGRectContainsPoint(rect, point))
- {
- CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect),
- point.y-CGRectGetMinY(rect));
- CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
- M80AttributedLabelURL *url = [self linkAtIndex:idx];
- if (url)
- {
- return url;
- }
- }
- }
- return nil;
- }
- - (id)linkDataForPoint:(CGPoint)point
- {
- M80AttributedLabelURL *url = [self urlForPoint:point];
- return url ? url.linkData : nil;
- }
- - (CGAffineTransform)transformForCoreText
- {
- return CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.f, -1.f);
- }
- - (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint) point
- {
- CGFloat ascent = 0.0f;
- CGFloat descent = 0.0f;
- CGFloat leading = 0.0f;
- CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
- CGFloat height = ascent + descent;
-
- return CGRectMake(point.x, point.y - descent, width, height);
- }
- - (M80AttributedLabelURL *)linkAtIndex:(CFIndex)index
- {
- for (M80AttributedLabelURL *url in _linkLocations)
- {
- if (NSLocationInRange(index, url.range))
- {
- return url;
- }
- }
- return nil;
- }
- - (CGRect)rectForRange:(NSRange)range
- inLine:(CTLineRef)line
- lineOrigin:(CGPoint)lineOrigin
- {
- CGRect rectForRange = CGRectZero;
- CFArrayRef runs = CTLineGetGlyphRuns(line);
- CFIndex runCount = CFArrayGetCount(runs);
-
- // Iterate through each of the "runs" (i.e. a chunk of text) and find the runs that
- // intersect with the range.
- for (CFIndex k = 0; k < runCount; k++)
- {
- CTRunRef run = CFArrayGetValueAtIndex(runs, k);
-
- CFRange stringRunRange = CTRunGetStringRange(run);
- NSRange lineRunRange = NSMakeRange(stringRunRange.location, stringRunRange.length);
- NSRange intersectedRunRange = NSIntersectionRange(lineRunRange, range);
-
- if (intersectedRunRange.length == 0)
- {
- // This run doesn't intersect the range, so skip it.
- continue;
- }
-
- CGFloat ascent = 0.0f;
- CGFloat descent = 0.0f;
- CGFloat leading = 0.0f;
-
- // Use of 'leading' doesn't properly highlight Japanese-character link.
- CGFloat width = (CGFloat)CTRunGetTypographicBounds(run,
- CFRangeMake(0, 0),
- &ascent,
- &descent,
- NULL); //&leading);
- CGFloat height = ascent + descent;
-
- CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil);
-
- CGRect linkRect = CGRectMake(lineOrigin.x + xOffset - leading, lineOrigin.y - descent, width + leading, height);
-
- linkRect.origin.y = roundf(linkRect.origin.y);
- linkRect.origin.x = roundf(linkRect.origin.x);
- linkRect.size.width = roundf(linkRect.size.width);
- linkRect.size.height = roundf(linkRect.size.height);
-
- rectForRange = CGRectIsEmpty(rectForRange) ? linkRect : CGRectUnion(rectForRange, linkRect);
- }
-
- return rectForRange;
- }
- - (void)appendAttachment:(M80AttributedLabelAttachment *)attachment
- {
- attachment.fontAscent = _fontAscent;
- attachment.fontDescent = _fontDescent;
- unichar objectReplacementChar = 0xFFFC;
- NSString *objectReplacementString = [NSString stringWithCharacters:&objectReplacementChar length:1];
- NSMutableAttributedString *attachText = [[NSMutableAttributedString alloc]initWithString:objectReplacementString];
-
- CTRunDelegateCallbacks callbacks;
- callbacks.version = kCTRunDelegateVersion1;
- callbacks.getAscent = ascentCallback;
- callbacks.getDescent = descentCallback;
- callbacks.getWidth = widthCallback;
- callbacks.dealloc = deallocCallback;
-
- CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (void *)attachment);
- NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)delegate,kCTRunDelegateAttributeName, nil];
- [attachText setAttributes:attr range:NSMakeRange(0, 1)];
- CFRelease(delegate);
-
- [_attachments addObject:attachment];
- [self appendAttributedText:attachText];
- }
- #pragma mark - 设置文本
- - (void)setText:(NSString *)text
- {
- NSAttributedString *attributedText = [self attributedString:text];
- [self setAttributedText:attributedText];
- }
- - (void)setAttributedText:(NSAttributedString *)attributedText
- {
- _attributedString = [[NSMutableAttributedString alloc]initWithAttributedString:attributedText];
- [self cleanAll];
- }
- - (NSString *)text
- {
- return [_attributedString string];
- }
- - (NSAttributedString *)attributedText
- {
- return [_attributedString copy];
- }
- #pragma mark - 添加文本
- - (void)appendText:(NSString *)text
- {
- NSAttributedString *attributedText = [self attributedString:text];
- [self appendAttributedText:attributedText];
- }
- - (void)appendAttributedText:(NSAttributedString *)attributedText
- {
- [_attributedString appendAttributedString:attributedText];
- [self resetTextFrame];
- }
- #pragma mark - 添加图片
- - (void)appendImage:(UIImage *)image
- {
- [self appendImage:image
- maxSize:image.size];
- }
- - (void)appendImage:(UIImage *)image
- maxSize:(CGSize)maxSize
- {
- [self appendImage:image
- maxSize:maxSize
- margin:UIEdgeInsetsZero];
- }
- - (void)appendImage:(UIImage *)image
- maxSize:(CGSize)maxSize
- margin:(UIEdgeInsets)margin
- {
- [self appendImage:image
- maxSize:maxSize
- margin:margin
- alignment:M80ImageAlignmentBottom];
- }
- - (void)appendImage:(UIImage *)image
- maxSize:(CGSize)maxSize
- margin:(UIEdgeInsets)margin
- alignment:(M80ImageAlignment)alignment
- {
- M80AttributedLabelAttachment *attachment = [M80AttributedLabelAttachment attachmentWith:image
- margin:margin
- alignment:alignment
- maxSize:maxSize];
- [self appendAttachment:attachment];
- }
- #pragma mark - 添加UI控件
- - (void)appendView:(UIView *)view
- {
- [self appendView:view
- margin:UIEdgeInsetsZero];
- }
- - (void)appendView:(UIView *)view
- margin:(UIEdgeInsets)margin
- {
- [self appendView:view
- margin:margin
- alignment:M80ImageAlignmentBottom];
- }
- - (void)appendView:(UIView *)view
- margin:(UIEdgeInsets)margin
- alignment:(M80ImageAlignment)alignment
- {
- M80AttributedLabelAttachment *attachment = [M80AttributedLabelAttachment attachmentWith:view
- margin:margin
- alignment:alignment
- maxSize:CGSizeZero];
- [self appendAttachment:attachment];
- }
- #pragma mark - 添加链接
- - (void)addCustomLink:(id)linkData
- forRange:(NSRange)range
- {
- [self addCustomLink:linkData
- forRange:range
- linkColor:self.linkColor];
-
- }
- - (void)addCustomLink:(id)linkData
- forRange:(NSRange)range
- linkColor:(UIColor *)color
- {
- M80AttributedLabelURL *url = [M80AttributedLabelURL urlWithLinkData:linkData
- range:range
- color:color];
- [_linkLocations addObject:url];
- [self resetTextFrame];
- }
- #pragma mark - 计算大小
- - (CGSize)sizeThatFits:(CGSize)size
- {
- NSAttributedString *drawString = [self attributedStringForDraw];
- if (drawString == nil)
- {
- return CGSizeZero;
- }
- CFAttributedStringRef attributedStringRef = (__bridge CFAttributedStringRef)drawString;
- CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedStringRef);
- CFRange range = CFRangeMake(0, 0);
- if (_numberOfLines > 0 && framesetter)
- {
- CGMutablePathRef path = CGPathCreateMutable();
- CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
- CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
- CFArrayRef lines = CTFrameGetLines(frame);
-
- if (nil != lines && CFArrayGetCount(lines) > 0)
- {
- NSInteger lastVisibleLineIndex = MIN(_numberOfLines, CFArrayGetCount(lines)) - 1;
- CTLineRef lastVisibleLine = CFArrayGetValueAtIndex(lines, lastVisibleLineIndex);
-
- CFRange rangeToLayout = CTLineGetStringRange(lastVisibleLine);
- range = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length);
- }
- CFRelease(frame);
- CFRelease(path);
- }
-
- CFRange fitCFRange = CFRangeMake(0, 0);
- CGSize newSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, range, NULL, size, &fitCFRange);
- if (framesetter)
- {
- CFRelease(framesetter);
- }
-
- //hack:
- //1.需要加上额外的一部分size,有些情况下计算出来的像素点并不是那么精准
- //2.iOS7 的 CTFramesetterSuggestFrameSizeWithConstraint s方法比较残,需要多加一部分 height
- //3.iOS7 多行中如果首行带有很多空格,会导致返回的 suggestionWidth 远小于真实 width ,那么多行情况下就是用传入的 width
- if (newSize.height < _fontHeight * 2) //单行
- {
- return CGSizeMake(ceilf(newSize.width) + 2.0, ceilf(newSize.height) + 4.0);
- }
- else
- {
- return CGSizeMake(size.width, ceilf(newSize.height) + 4.0);
- }
- }
- - (CGSize)intrinsicContentSize
- {
- return [self sizeThatFits:CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)];
- }
- #pragma mark - 绘制方法
- - (void)drawRect:(CGRect)rect
- {
- CGContextRef ctx = UIGraphicsGetCurrentContext();
- if (ctx == nil)
- {
- return;
- }
- CGContextSaveGState(ctx);
- CGAffineTransform transform = [self transformForCoreText];
- CGContextConcatCTM(ctx, transform);
-
- [self recomputeLinksIfNeeded];
-
- NSAttributedString *drawString = [self attributedStringForDraw];
- if (drawString)
- {
- [self prepareTextFrame:drawString rect:rect];
- [self drawHighlightWithRect:rect];
- [self drawAttachments];
- [self drawShadow:ctx];
- [self drawText:drawString
- rect:rect
- context:ctx];
- }
- CGContextRestoreGState(ctx);
- }
- - (void)prepareTextFrame:(NSAttributedString *)string
- rect:(CGRect)rect
- {
- if (_textFrame == nil)
- {
- CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string);
- CGMutablePathRef path = CGPathCreateMutable();
- CGPathAddRect(path, nil,rect);
- _textFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
- CGPathRelease(path);
- CFRelease(framesetter);
- }
- }
- - (void)drawHighlightWithRect:(CGRect)rect
- {
- if (self.touchedLink && self.highlightColor)
- {
- [self.highlightColor setFill];
- NSRange linkRange = self.touchedLink.range;
-
- CFArrayRef lines = CTFrameGetLines(_textFrame);
- CFIndex count = CFArrayGetCount(lines);
- CGPoint lineOrigins[count];
- CTFrameGetLineOrigins(_textFrame, CFRangeMake(0, 0), lineOrigins);
- NSInteger numberOfLines = [self numberOfDisplayedLines];
-
- CGContextRef ctx = UIGraphicsGetCurrentContext();
-
- for (CFIndex i = 0; i < numberOfLines; i++)
- {
- CTLineRef line = CFArrayGetValueAtIndex(lines, i);
-
- CFRange stringRange = CTLineGetStringRange(line);
- NSRange lineRange = NSMakeRange(stringRange.location, stringRange.length);
- NSRange intersectedRange = NSIntersectionRange(lineRange, linkRange);
- if (intersectedRange.length == 0) {
- continue;
- }
-
- CGRect highlightRect = [self rectForRange:linkRange
- inLine:line
- lineOrigin:lineOrigins[i]];
- highlightRect = CGRectOffset(highlightRect, 0, -rect.origin.y);
- if (!CGRectIsEmpty(highlightRect))
- {
- CGFloat pi = (CGFloat)M_PI;
-
- CGFloat radius = 1.0f;
- CGContextMoveToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + radius);
- CGContextAddLineToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + highlightRect.size.height - radius);
- CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + highlightRect.size.height - radius,
- radius, pi, pi / 2.0f, 1.0f);
- CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width - radius,
- highlightRect.origin.y + highlightRect.size.height);
- CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius,
- highlightRect.origin.y + highlightRect.size.height - radius, radius, pi / 2, 0.0f, 1.0f);
- CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width, highlightRect.origin.y + radius);
- CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + radius,
- radius, 0.0f, -pi / 2.0f, 1.0f);
- CGContextAddLineToPoint(ctx, highlightRect.origin.x + radius, highlightRect.origin.y);
- CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + radius, radius,
- -pi / 2, pi, 1);
- CGContextFillPath(ctx);
- }
- }
-
- }
- }
- - (void)drawShadow:(CGContextRef)ctx
- {
- if (self.shadowColor)
- {
- CGContextSetShadowWithColor(ctx, self.shadowOffset, self.shadowBlur, self.shadowColor.CGColor);
- }
- }
- - (void)drawText:(NSAttributedString *)attributedString
- rect:(CGRect)rect
- context:(CGContextRef)context
- {
- if (_textFrame)
- {
- if (_numberOfLines > 0)
- {
- CFArrayRef lines = CTFrameGetLines(_textFrame);
- NSInteger numberOfLines = [self numberOfDisplayedLines];
-
- CGPoint lineOrigins[numberOfLines];
- CTFrameGetLineOrigins(_textFrame, CFRangeMake(0, numberOfLines), lineOrigins);
-
- 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 &&
- _lineBreakMode == kCTLineBreakByTruncatingTail)
- {
- //找到最后一行并检查是否需要 truncatingTail
- 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:M80EllipsesCharacter
- attributes:tokenAttributes];
- CTLineRef truncationToken = CTLineCreateWithAttributedString((CFAttributedStringRef)tokenString);
-
- NSMutableAttributedString *truncationString = [[attributedString attributedSubstringFromRange:NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy];
-
- if (lastLineRange.length > 0)
- {
- //移除掉最后一个对象...(其实这个地方有点问题,也有可能需要移除最后 2 个对象,因为 attachment 宽度的关系)
- [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)
- {
- truncatedLine = CFRetain(truncationToken);
- }
- CFRelease(truncationLine);
- CFRelease(truncationToken);
-
- CTLineDraw(truncatedLine, context);
- CFRelease(truncatedLine);
-
-
- shouldDrawLine = NO;
- }
- }
- if(shouldDrawLine)
- {
- CTLineDraw(line, context);
- }
- }
- }
- else
- {
- CTFrameDraw(_textFrame,context);
- }
- }
- }
- - (void)drawAttachments
- {
- if ([_attachments count] == 0)
- {
- return;
- }
- CGContextRef ctx = UIGraphicsGetCurrentContext();
- if (ctx == nil)
- {
- return;
- }
-
- CFArrayRef lines = CTFrameGetLines(_textFrame);
- CFIndex lineCount = CFArrayGetCount(lines);
- CGPoint lineOrigins[lineCount];
- CTFrameGetLineOrigins(_textFrame, CFRangeMake(0, 0), lineOrigins);
- NSInteger numberOfLines = [self numberOfDisplayedLines];
- for (CFIndex i = 0; i < numberOfLines; i++)
- {
- CTLineRef line = CFArrayGetValueAtIndex(lines, i);
- CFArrayRef runs = CTLineGetGlyphRuns(line);
- CFIndex runCount = CFArrayGetCount(runs);
- CGPoint lineOrigin = lineOrigins[i];
- CGFloat lineAscent;
- CGFloat lineDescent;
- CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, NULL);
- CGFloat lineHeight = lineAscent + lineDescent;
- CGFloat lineBottomY = lineOrigin.y - lineDescent;
-
- //遍历以找到对应的 attachment 进行绘制
- for (CFIndex k = 0; k < runCount; k++)
- {
- CTRunRef run = CFArrayGetValueAtIndex(runs, k);
- NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);
- CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
- if (nil == delegate)
- {
- continue;
- }
- M80AttributedLabelAttachment* attributedImage = (M80AttributedLabelAttachment *)CTRunDelegateGetRefCon(delegate);
-
- CGFloat ascent = 0.0f;
- CGFloat descent = 0.0f;
- CGFloat width = (CGFloat)CTRunGetTypographicBounds(run,
- CFRangeMake(0, 0),
- &ascent,
- &descent,
- NULL);
-
- CGFloat imageBoxHeight = [attributedImage boxSize].height;
- CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil);
-
- CGFloat imageBoxOriginY = 0.0f;
- switch (attributedImage.alignment)
- {
- case M80ImageAlignmentTop:
- imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight);
- break;
- case M80ImageAlignmentCenter:
- imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight) / 2.0;
- break;
- case M80ImageAlignmentBottom:
- imageBoxOriginY = lineBottomY;
- break;
- }
-
- CGRect rect = CGRectMake(lineOrigin.x + xOffset, imageBoxOriginY, width, imageBoxHeight);
- UIEdgeInsets flippedMargins = attributedImage.margin;
- CGFloat top = flippedMargins.top;
- flippedMargins.top = flippedMargins.bottom;
- flippedMargins.bottom = top;
-
- CGRect attatchmentRect = UIEdgeInsetsInsetRect(rect, flippedMargins);
-
- if (i == numberOfLines - 1 &&
- k >= runCount - 2 &&
- _lineBreakMode == kCTLineBreakByTruncatingTail)
- {
- //最后行最后的2个CTRun需要做额外判断
- CGFloat attachmentWidth = CGRectGetWidth(attatchmentRect);
- const CGFloat kMinEllipsesWidth = attachmentWidth;
- if (CGRectGetWidth(self.bounds) - CGRectGetMinX(attatchmentRect) - attachmentWidth < kMinEllipsesWidth)
- {
- continue;
- }
- }
-
-
-
- id content = attributedImage.content;
- if ([content isKindOfClass:[UIImage class]])
- {
- CGContextDrawImage(ctx, attatchmentRect, ((UIImage *)content).CGImage);
- }
- else if ([content isKindOfClass:[UIView class]])
- {
- UIView *view = (UIView *)content;
- if (view.superview == nil)
- {
- [self addSubview:view];
- }
- CGRect viewFrame = CGRectMake(attatchmentRect.origin.x,
- self.bounds.size.height - attatchmentRect.origin.y - attatchmentRect.size.height,
- attatchmentRect.size.width,
- attatchmentRect.size.height);
- [view setFrame:viewFrame];
- }
- else
- {
- NSLog(@"Attachment Content Not Supported %@",content);
- }
-
- }
- }
- }
- #pragma mark - 点击事件处理
- - (BOOL)onLabelClick:(CGPoint)point
- {
- id linkData = [self linkDataForPoint:point];
- if (linkData)
- {
- if (_delegate && [_delegate respondsToSelector:@selector(m80AttributedLabel:clickedOnLink:)])
- {
- [_delegate m80AttributedLabel:self clickedOnLink:linkData];
- }
- else
- {
- NSURL *url = nil;
- if ([linkData isKindOfClass:[NSString class]])
- {
- url = [NSURL URLWithString:linkData];
- }
- else if([linkData isKindOfClass:[NSURL class]])
- {
- url = linkData;
- }
- if (url)
- {
- [[UIApplication sharedApplication] openURL:url];
- }
- }
- return YES;
- }
-
- return NO;
- }
- #pragma mark - 链接处理
- - (void)recomputeLinksIfNeeded
- {
- const NSInteger kMinHttpLinkLength = 5;
- if (!_autoDetectLinks || _linkDetected)
- {
- return;
- }
- NSString *text = [[_attributedString string] copy];
- NSUInteger length = [text length];
- if (length <= kMinHttpLinkLength)
- {
- return;
- }
- BOOL sync = length <= M80MinAsyncDetectLinkLength;
- [self computeLink:text
- sync:sync];
- }
- - (void)computeLink:(NSString *)text
- sync:(BOOL)sync
- {
- __weak typeof(self) weakSelf = self;
- typedef void (^LinkBlock) (NSArray *);
- LinkBlock block = ^(NSArray *links)
- {
- weakSelf.linkDetected = YES;
- if ([links count])
- {
- for (M80AttributedLabelURL *link in links)
- {
- [weakSelf addAutoDetectedLink:link];
- }
- [weakSelf resetTextFrame];
- }
- };
-
- if (sync)
- {
- _ignoreRedraw = YES;
- NSArray *links = [M80AttributedLabelURL detectLinks:text];
- block(links);
- _ignoreRedraw = NO;
- }
- else
- {
- dispatch_async(get_m80_attributed_label_parse_queue(), ^{
-
- NSArray *links = [M80AttributedLabelURL detectLinks:text];
-
- dispatch_async(dispatch_get_main_queue(), ^{
- NSString *plainText = [[weakSelf attributedString] string];
- if ([plainText isEqualToString:text])
- {
- block(links);
- }
- });
- });
- }
- }
- - (void)addAutoDetectedLink:(M80AttributedLabelURL *)link
- {
- NSRange range = link.range;
- for (M80AttributedLabelURL *url in _linkLocations)
- {
- if (NSIntersectionRange(range, url.range).length != 0)
- {
- return;
- }
- }
- [self addCustomLink:link.linkData
- forRange:link.range];
- }
- #pragma mark - 点击事件相应
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- if (self.touchedLink == nil)
- {
- UITouch *touch = [touches anyObject];
- CGPoint point = [touch locationInView:self];
- self.touchedLink = [self urlForPoint:point];
- }
-
-
- if (self.touchedLink)
- {
- [self setNeedsDisplay];
- }
- else
- {
- [super touchesBegan:touches withEvent:event];
- }
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesMoved:touches withEvent:event];
- UITouch *touch = [touches anyObject];
- CGPoint point = [touch locationInView:self];
-
- M80AttributedLabelURL *touchedLink = [self urlForPoint:point];
- if (self.touchedLink != touchedLink)
- {
- self.touchedLink = touchedLink;
- [self setNeedsDisplay];
- }
- }
- - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesCancelled:touches withEvent:event];
- if (self.touchedLink)
- {
- self.touchedLink = nil;
- [self setNeedsDisplay];
- }
- }
- - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- {
- UITouch *touch = [touches anyObject];
- CGPoint point = [touch locationInView:self];
- if(![self onLabelClick:point])
- {
- [super touchesEnded:touches withEvent:event];
- }
- if (self.touchedLink)
- {
- self.touchedLink = nil;
- [self setNeedsDisplay];
- }
- }
- - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- {
- M80AttributedLabelURL *touchedLink = [self urlForPoint:point];
- if (touchedLink == nil)
- {
- NSArray *subViews = [self subviews];
- for (UIView *view in subViews)
- {
- CGPoint hitPoint = [view convertPoint:point
- fromView:self];
-
- UIView *hitTestView = [view hitTest:hitPoint
- withEvent:event];
- if (hitTestView)
- {
- return hitTestView;
- }
- }
- return nil;
- }
- else
- {
- return self;
- }
- }
- #pragma mark - 设置自定义的连接检测block
- + (void)setCustomDetectMethod:(M80CustomDetectLinkBlock)block
- {
- [M80AttributedLabelURL setCustomDetectMethod:block];
- }
- @end
|