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.
1019 lines
32 KiB
1019 lines
32 KiB
4 years ago
|
//
|
||
|
// 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
|