You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1018 lines
32 KiB
1018 lines
32 KiB
// |
|
// TYAttributedLabel.m |
|
// TYAttributedLabelDemo |
|
// |
|
// Created by tanyang on 15/4/8. |
|
// Copyright (c) 2015年 tanyang. All rights reserved. |
|
// |
|
|
|
#import "TYAttributedLabel.h" |
|
#import <CoreText/CoreText.h> |
|
|
|
#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<TYDrawStorageProtocol> drawStorage, CGRect rect))block; |
|
|
|
- (BOOL)enumerateRunRectContainPoint:(CGPoint)point |
|
viewHeight:(CGFloat)viewHeight |
|
successBlock:(void (^)(id<TYTextStorageProtocol> textStorage))successBlock; |
|
|
|
- (BOOL)enumerateLinkRectContainPoint:(CGPoint)point |
|
viewHeight:(CGFloat)viewHeight |
|
successBlock:(void (^)(id<TYLinkStorageProtocol> linkStorage))successBlock; |
|
|
|
@end |
|
|
|
@interface TYAttributedLabel ()<UIGestureRecognizerDelegate> |
|
{ |
|
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<TYAttributedLabelDelegate>)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<TYTextStorageProtocol>)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<TYDrawStorageProtocol> drawStorage, CGRect rect) { |
|
if ([drawStorage conformsToProtocol:@protocol(TYViewStorageProtocol) ]) { |
|
[(id<TYViewStorageProtocol>)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<TYTextStorageProtocol> 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<TYTextStorageProtocol> 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<TYLinkStorageProtocol> 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<TYLinkStorageProtocol> 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<TYAppendTextStorageProtocol>)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
|
|
|