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.
679 lines
23 KiB
679 lines
23 KiB
4 years ago
|
//
|
||
|
// TYTextContainer.m
|
||
|
// TYAttributedLabelDemo
|
||
|
//
|
||
|
// Created by tanyang on 15/6/4.
|
||
|
// Copyright (c) 2015年 tanyang. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "TYTextContainer.h"
|
||
|
|
||
|
#define kTextColor [UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1]
|
||
|
#define kLinkColor [UIColor colorWithRed:0/255.0 green:91/255.0 blue:255/255.0 alpha:1]
|
||
|
|
||
|
// this code quote TTTAttributedLabel
|
||
|
static inline CGSize CTFramesetterSuggestFrameSizeForAttributedStringWithConstraints(CTFramesetterRef framesetter, NSAttributedString *attributedString, CGSize size, NSUInteger numberOfLines) {
|
||
|
CFRange rangeToSize = CFRangeMake(0, (CFIndex)[attributedString length]);
|
||
|
CGSize constraints = CGSizeMake(size.width, MAXFLOAT);
|
||
|
|
||
|
if (numberOfLines > 0) {
|
||
|
// If the line count of the label more than 1, limit the range to size to the number of lines that have been set
|
||
|
CGMutablePathRef path = CGPathCreateMutable();
|
||
|
CGPathAddRect(path, NULL, CGRectMake(0.0f, 0.0f, constraints.width, MAXFLOAT));
|
||
|
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
|
||
|
CFArrayRef lines = CTFrameGetLines(frame);
|
||
|
|
||
|
if (CFArrayGetCount(lines) > 0) {
|
||
|
NSInteger lastVisibleLineIndex = MIN((CFIndex)numberOfLines, CFArrayGetCount(lines)) - 1;
|
||
|
CTLineRef lastVisibleLine = CFArrayGetValueAtIndex(lines, lastVisibleLineIndex);
|
||
|
|
||
|
CFRange rangeToLayout = CTLineGetStringRange(lastVisibleLine);
|
||
|
rangeToSize = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length);
|
||
|
}
|
||
|
|
||
|
CFRelease(frame);
|
||
|
CFRelease(path);
|
||
|
}
|
||
|
|
||
|
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, rangeToSize, NULL, constraints, NULL);
|
||
|
|
||
|
return CGSizeMake(ceil(suggestedSize.width), ceil(suggestedSize.height));
|
||
|
}
|
||
|
|
||
|
@interface TYTextContainer()
|
||
|
@property (nonatomic, strong) NSMutableArray *textStorageArray; // run数组
|
||
|
@property (nonatomic, strong) NSArray *textStorages; // run array copy
|
||
|
|
||
|
@property (nonatomic, strong) NSDictionary *drawRectDictionary;
|
||
|
@property (nonatomic, strong) NSDictionary *runRectDictionary; // runRect字典
|
||
|
@property (nonatomic, strong) NSDictionary *linkRectDictionary; // linkRect字典
|
||
|
|
||
|
@property (nonatomic, assign) NSInteger replaceStringNum; // 图片替换字符数
|
||
|
@property (nonatomic, strong) NSMutableAttributedString *attString;
|
||
|
@property (nonatomic, assign) CTFrameRef frameRef;
|
||
|
@property (nonatomic, assign) CGFloat textHeight;
|
||
|
@property (nonatomic, assign) CGFloat textWidth;
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation TYTextContainer
|
||
|
|
||
|
- (instancetype)init
|
||
|
{
|
||
|
if (self = [super init]) {
|
||
|
[self setupProperty];
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
#pragma mark - getter
|
||
|
|
||
|
- (NSMutableArray *)textStorageArray
|
||
|
{
|
||
|
if (_textStorageArray == nil) {
|
||
|
_textStorageArray = [NSMutableArray array];
|
||
|
}
|
||
|
return _textStorageArray;
|
||
|
}
|
||
|
|
||
|
- (NSString *)text{
|
||
|
return _attString.string;
|
||
|
}
|
||
|
|
||
|
- (NSAttributedString *)attributedText
|
||
|
{
|
||
|
return [_attString copy];
|
||
|
}
|
||
|
|
||
|
- (NSAttributedString *)createAttributedString
|
||
|
{
|
||
|
[self addTextStoragesWithAtrributedString:_attString];
|
||
|
if (_attString == nil) {
|
||
|
_attString = [[NSMutableAttributedString alloc] init];
|
||
|
}
|
||
|
return [_attString copy];
|
||
|
}
|
||
|
|
||
|
#pragma mark - setter
|
||
|
- (void)setupProperty
|
||
|
{
|
||
|
_font = [UIFont systemFontOfSize:15];
|
||
|
_characterSpacing = 1;
|
||
|
_linesSpacing = 2;
|
||
|
_paragraphSpacing = 0;
|
||
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED <= 9000
|
||
|
_textAlignment = kCTLeftTextAlignment;
|
||
|
#else
|
||
|
_textAlignment = kCTTextAlignmentLeft;
|
||
|
#endif
|
||
|
_lineBreakMode = kCTLineBreakByCharWrapping;
|
||
|
_textColor = kTextColor;
|
||
|
_linkColor = kLinkColor;
|
||
|
_replaceStringNum = 0;
|
||
|
}
|
||
|
|
||
|
- (void)resetAllAttributed
|
||
|
{
|
||
|
[self resetRectDictionary];
|
||
|
_textStorageArray = nil;
|
||
|
_textStorages = nil;
|
||
|
_replaceStringNum = 0;
|
||
|
}
|
||
|
|
||
|
- (void)resetRectDictionary
|
||
|
{
|
||
|
_drawRectDictionary = nil;
|
||
|
_linkRectDictionary = nil;
|
||
|
_runRectDictionary = nil;
|
||
|
}
|
||
|
|
||
|
- (void)resetFrameRef
|
||
|
{
|
||
|
if (_frameRef) {
|
||
|
CFRelease(_frameRef);
|
||
|
_frameRef = nil;
|
||
|
}
|
||
|
_textHeight = 0;
|
||
|
}
|
||
|
|
||
|
- (void)setText:(NSString *)text
|
||
|
{
|
||
|
_attString = [self createTextAttibuteStringWithText:text];
|
||
|
[self resetAllAttributed];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
|
||
|
- (void)setAttributedText:(NSAttributedString *)attributedText
|
||
|
{
|
||
|
if (attributedText == nil) {
|
||
|
_attString = [[NSMutableAttributedString alloc] init];
|
||
|
}else if ([attributedText isKindOfClass:[NSMutableAttributedString class]]) {
|
||
|
_attString = (NSMutableAttributedString *)attributedText;
|
||
|
}else {
|
||
|
_attString = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
|
||
|
}
|
||
|
[self resetAllAttributed];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
|
||
|
- (void)setTextColor:(UIColor *)textColor
|
||
|
{
|
||
|
if (textColor && _textColor != textColor){
|
||
|
_textColor = textColor;
|
||
|
|
||
|
[_attString addAttributeTextColor:textColor];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setFont:(UIFont *)font
|
||
|
{
|
||
|
if (font && _font != font){
|
||
|
_font = font;
|
||
|
|
||
|
[_attString addAttributeFont:font];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setStrokeWidth:(unichar)strokeWidth
|
||
|
{
|
||
|
if (_strokeWidth != strokeWidth) {
|
||
|
_strokeWidth = strokeWidth;
|
||
|
[_attString addAttributeStrokeWidth:strokeWidth strokeColor:_strokeColor];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setStrokeColor:(UIColor *)strokeColor
|
||
|
{
|
||
|
if (strokeColor && _strokeColor != strokeColor) {
|
||
|
_strokeColor = strokeColor;
|
||
|
[_attString addAttributeStrokeWidth:_strokeWidth strokeColor:strokeColor];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setCharacterSpacing:(unichar)characterSpacing
|
||
|
{
|
||
|
if (_characterSpacing != characterSpacing) {
|
||
|
_characterSpacing = characterSpacing;
|
||
|
|
||
|
[_attString addAttributeCharacterSpacing:characterSpacing];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setLinesSpacing:(CGFloat)linesSpacing
|
||
|
{
|
||
|
if (_linesSpacing != linesSpacing) {
|
||
|
_linesSpacing = linesSpacing;
|
||
|
|
||
|
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setParagraphSpacing:(CGFloat)paragraphSpacing
|
||
|
{
|
||
|
if (_paragraphSpacing != paragraphSpacing) {
|
||
|
_paragraphSpacing = paragraphSpacing;
|
||
|
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setTextAlignment:(CTTextAlignment)textAlignment
|
||
|
{
|
||
|
if (_textAlignment != textAlignment) {
|
||
|
_textAlignment = textAlignment;
|
||
|
|
||
|
[self addAttributeAlignmentStyle:textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)setLineBreakMode:(CTLineBreakMode)lineBreakMode
|
||
|
{
|
||
|
if (_lineBreakMode != lineBreakMode) {
|
||
|
_lineBreakMode = lineBreakMode;
|
||
|
|
||
|
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
|
||
|
[self resetFrameRef];
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma mark - create text attibuteString
|
||
|
- (NSMutableAttributedString *)createTextAttibuteStringWithText:(NSString *)text
|
||
|
{
|
||
|
if (text.length <= 0) {
|
||
|
return [[NSMutableAttributedString alloc] init];
|
||
|
}
|
||
|
// 创建属性文本
|
||
|
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:text];
|
||
|
|
||
|
// 添加文本颜色 字体属性
|
||
|
[self addTextColorAndFontWithAtrributedString:attString];
|
||
|
|
||
|
// 添加文本段落样式
|
||
|
[self addTextParaphStyleWithAtrributedString:attString];
|
||
|
|
||
|
return attString;
|
||
|
}
|
||
|
|
||
|
// 添加文本颜色 字体属性
|
||
|
- (void)addTextColorAndFontWithAtrributedString:(NSMutableAttributedString *)attString
|
||
|
{
|
||
|
// 添加文本字体
|
||
|
[attString addAttributeFont:_font];
|
||
|
|
||
|
// 添加文本颜色
|
||
|
[attString addAttributeTextColor:_textColor];
|
||
|
|
||
|
// 添加空心字体
|
||
|
if (_strokeWidth > 0) {
|
||
|
[attString addAttributeStrokeWidth:_strokeWidth strokeColor:_strokeColor];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// 添加文本段落样式
|
||
|
- (void)addTextParaphStyleWithAtrributedString:(NSMutableAttributedString *)attString
|
||
|
{
|
||
|
// 字体间距
|
||
|
if (_characterSpacing)
|
||
|
{
|
||
|
[attString addAttributeCharacterSpacing:_characterSpacing];
|
||
|
}
|
||
|
|
||
|
// 添加文本段落样式
|
||
|
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
|
||
|
}
|
||
|
|
||
|
- (void)addAttributeAlignmentStyle:(CTTextAlignment)textAlignment
|
||
|
lineSpaceStyle:(CGFloat)linesSpacing
|
||
|
paragraphSpaceStyle:(CGFloat)paragraphSpacing
|
||
|
lineBreakStyle:(CTLineBreakMode)lineBreakMode
|
||
|
{
|
||
|
if (lineBreakMode == kCTLineBreakByTruncatingTail)
|
||
|
{
|
||
|
lineBreakMode = _numberOfLines == 1 ? kCTLineBreakByCharWrapping : kCTLineBreakByWordWrapping;
|
||
|
}
|
||
|
[_attString addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:lineBreakMode];
|
||
|
}
|
||
|
|
||
|
#pragma mark - add text storage atrributed
|
||
|
- (void)addTextStoragesWithAtrributedString:(NSMutableAttributedString *)attString
|
||
|
{
|
||
|
if (attString && _textStorageArray.count > 0) {
|
||
|
|
||
|
// 排序range
|
||
|
[self sortTextStorageArray:_textStorageArray];
|
||
|
|
||
|
for (id<TYTextStorageProtocol> textStorage in _textStorageArray) {
|
||
|
|
||
|
// 修正图片替换字符来的误差
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol) ]) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYLinkStorageProtocol)]) {
|
||
|
if (!((id<TYLinkStorageProtocol>)textStorage).textColor) {
|
||
|
((id<TYLinkStorageProtocol>)textStorage).textColor = _linkColor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 验证范围
|
||
|
if (NSMaxRange(textStorage.range) <= attString.length) {
|
||
|
[textStorage addTextStorageWithAttributedString:attString];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
for (id<TYTextStorageProtocol> textStorage in _textStorageArray) {
|
||
|
textStorage.realRange = NSMakeRange(textStorage.range.location-_replaceStringNum, textStorage.range.length);
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol)]) {
|
||
|
id<TYDrawStorageProtocol> drawStorage = (id<TYDrawStorageProtocol>)textStorage;
|
||
|
NSInteger currentLenght = attString.length;
|
||
|
[drawStorage setTextfontAscent:_font.ascender descent:_font.descender];
|
||
|
[drawStorage currentReplacedStringNum:_replaceStringNum];
|
||
|
[drawStorage addTextStorageWithAttributedString:attString];
|
||
|
_replaceStringNum += currentLenght - attString.length;
|
||
|
}
|
||
|
}
|
||
|
_textStorages = [_textStorageArray copy];
|
||
|
[_textStorageArray removeAllObjects];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)sortTextStorageArray:(NSMutableArray *)textStorageArray
|
||
|
{
|
||
|
[textStorageArray sortUsingComparator:^NSComparisonResult(id<TYTextStorageProtocol> obj1, id<TYTextStorageProtocol> obj2) {
|
||
|
if (obj1.range.location < obj2.range.location) {
|
||
|
return NSOrderedAscending;
|
||
|
} else if (obj1.range.location > obj2.range.location){
|
||
|
return NSOrderedDescending;
|
||
|
}else {
|
||
|
return obj1.range.length > obj2.range.length ? NSOrderedAscending:NSOrderedDescending;
|
||
|
}
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
- (void)saveTextStorageRectWithFrame:(CTFrameRef)frame
|
||
|
{
|
||
|
if (!frame) {
|
||
|
return;
|
||
|
}
|
||
|
// 获取每行
|
||
|
CFArrayRef lines = CTFrameGetLines(frame);
|
||
|
CGPoint lineOrigins[CFArrayGetCount(lines)];
|
||
|
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);
|
||
|
CGFloat viewWidth = _textWidth;
|
||
|
|
||
|
NSInteger numberOfLines = _numberOfLines > 0 ? MIN(_numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
|
||
|
|
||
|
NSMutableDictionary *runRectDictionary = [NSMutableDictionary dictionary];
|
||
|
NSMutableDictionary *linkRectDictionary = [NSMutableDictionary dictionary];
|
||
|
NSMutableDictionary *drawRectDictionary = [NSMutableDictionary dictionary];
|
||
|
// 获取每行有多少run
|
||
|
for (int i = 0; i < numberOfLines; i++) {
|
||
|
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
|
||
|
CGFloat lineAscent;
|
||
|
CGFloat lineDescent;
|
||
|
CGFloat lineLeading;
|
||
|
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
|
||
|
|
||
|
CFArrayRef runs = CTLineGetGlyphRuns(line);
|
||
|
// 获得每行的run
|
||
|
for (int j = 0; j < CFArrayGetCount(runs); j++) {
|
||
|
CGFloat runAscent;
|
||
|
CGFloat runDescent;
|
||
|
CGPoint lineOrigin = lineOrigins[i];
|
||
|
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
|
||
|
// run的属性字典
|
||
|
NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
|
||
|
id<TYTextStorageProtocol> textStorage = [attributes objectForKey:kTYTextRunAttributedName];
|
||
|
|
||
|
if (textStorage) {
|
||
|
CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);
|
||
|
|
||
|
if (viewWidth > 0 && runWidth > viewWidth) {
|
||
|
runWidth = viewWidth;
|
||
|
}
|
||
|
CGRect runRect = CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL), lineOrigin.y - runDescent, runWidth, runAscent + runDescent);
|
||
|
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol)]) {
|
||
|
[drawRectDictionary setObject:textStorage forKey:[NSValue valueWithCGRect:runRect]];
|
||
|
} else if ([textStorage conformsToProtocol:@protocol(TYLinkStorageProtocol)]) {
|
||
|
[linkRectDictionary setObject:textStorage forKey:[NSValue valueWithCGRect:runRect]];
|
||
|
}
|
||
|
|
||
|
[runRectDictionary setObject:textStorage forKey:[NSValue valueWithCGRect:runRect]];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (drawRectDictionary.count > 0) {
|
||
|
_drawRectDictionary = [drawRectDictionary copy];
|
||
|
}else {
|
||
|
_drawRectDictionary = nil;
|
||
|
}
|
||
|
|
||
|
if (runRectDictionary.count > 0) {
|
||
|
// 添加响应点击rect
|
||
|
[self addRunRectDictionary:[runRectDictionary copy]];
|
||
|
}
|
||
|
|
||
|
if (linkRectDictionary.count > 0) {
|
||
|
_linkRectDictionary = [linkRectDictionary copy];
|
||
|
}else {
|
||
|
_linkRectDictionary = nil;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 添加响应点击rect
|
||
|
- (void)addRunRectDictionary:(NSDictionary *)runRectDictionary
|
||
|
{
|
||
|
if (runRectDictionary.count < _runRectDictionary.count) {
|
||
|
NSMutableArray *drawStorageArray = [[_runRectDictionary allValues]mutableCopy];
|
||
|
// 剔除已经画出来的
|
||
|
[drawStorageArray removeObjectsInArray:[runRectDictionary allValues]];
|
||
|
|
||
|
// 遍历不会画出来的
|
||
|
for (id<TYTextStorageProtocol>drawStorage in drawStorageArray) {
|
||
|
if ([drawStorage conformsToProtocol:@protocol(TYViewStorageProtocol)]) {
|
||
|
[(id<TYViewStorageProtocol>)drawStorage didNotDrawRun];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
_runRectDictionary = runRectDictionary;
|
||
|
}
|
||
|
|
||
|
- (CGSize)getSuggestedSizeWithFramesetter:(CTFramesetterRef)framesetter width:(CGFloat)width
|
||
|
{
|
||
|
if (_attString == nil || width <= 0) {
|
||
|
return CGSizeZero;
|
||
|
}
|
||
|
|
||
|
if (_textHeight > 0) {
|
||
|
return CGSizeMake(_textWidth > 0 ? _textWidth : width, _textHeight);
|
||
|
}
|
||
|
|
||
|
// 是否需要更新frame
|
||
|
if (framesetter == nil) {
|
||
|
|
||
|
framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)[self createAttributedString]);
|
||
|
}else {
|
||
|
CFRetain(framesetter);
|
||
|
}
|
||
|
|
||
|
// 获得建议的size
|
||
|
CGSize suggestedSize = CTFramesetterSuggestFrameSizeForAttributedStringWithConstraints(framesetter, _attString, CGSizeMake(width,MAXFLOAT), _numberOfLines);
|
||
|
|
||
|
CFRelease(framesetter);
|
||
|
|
||
|
return CGSizeMake(_isWidthToFit ? suggestedSize.width : width, suggestedSize.height+1);
|
||
|
}
|
||
|
- (CGFloat)getHeightWithFramesetter:(CTFramesetterRef)framesetter width:(CGFloat)width
|
||
|
{
|
||
|
return [self getSuggestedSizeWithFramesetter:framesetter width:width].height;
|
||
|
}
|
||
|
|
||
|
- (CTFrameRef)createFrameRefWithFramesetter:(CTFramesetterRef)framesetter textSize:(CGSize)textSize
|
||
|
{
|
||
|
// 这里你需要创建一个用于绘制文本的路径区域,通过 self.bounds 使用整个视图矩形区域创建 CGPath 引用。
|
||
|
CGMutablePathRef path = CGPathCreateMutable();
|
||
|
CGFloat textHeight = [self getHeightWithFramesetter:framesetter width:textSize.width];
|
||
|
CGPathAddRect(path, NULL, CGRectMake(0, 0, textSize.width, MAX(textHeight, textSize.height)));
|
||
|
|
||
|
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [_attString length]), path, NULL);
|
||
|
CFRelease(path);
|
||
|
return frameRef;
|
||
|
}
|
||
|
|
||
|
- (instancetype)createTextContainerWithTextWidth:(CGFloat)textWidth
|
||
|
{
|
||
|
return [self createTextContainerWithContentSize:CGSizeMake(textWidth, 0)];
|
||
|
}
|
||
|
|
||
|
- (instancetype)createTextContainerWithContentSize:(CGSize)contentSize
|
||
|
{
|
||
|
if (_frameRef) {
|
||
|
return self;
|
||
|
}
|
||
|
NSAttributedString *attStr = [self createAttributedString];
|
||
|
// 创建CTFramesetter
|
||
|
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attStr);
|
||
|
|
||
|
// 获得建议的size
|
||
|
CGSize size = [self getSuggestedSizeWithFramesetter:framesetter width:contentSize.width];
|
||
|
_textWidth = size.width;
|
||
|
_textHeight = size.height;
|
||
|
|
||
|
// 创建CTFrameRef
|
||
|
_frameRef = [self createFrameRefWithFramesetter:framesetter textSize:CGSizeMake(_textWidth, contentSize.height > 0 ? contentSize.height : _textHeight)];
|
||
|
|
||
|
// 释放内存
|
||
|
CFRelease(framesetter);
|
||
|
|
||
|
// 保存run rect
|
||
|
[self saveTextStorageRectWithFrame:_frameRef];
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
#pragma mark - enumerate runRect
|
||
|
|
||
|
- (BOOL)existRunRectDictionary
|
||
|
{
|
||
|
return _runRectDictionary.count != 0;
|
||
|
}
|
||
|
|
||
|
- (BOOL)existLinkRectDictionary
|
||
|
{
|
||
|
return _linkRectDictionary.count != 0;
|
||
|
}
|
||
|
|
||
|
- (BOOL)existDrawRectDictionary
|
||
|
{
|
||
|
return _drawRectDictionary.count != 0;
|
||
|
}
|
||
|
|
||
|
- (void)enumerateDrawRectDictionaryUsingBlock:(void (^)(id<TYDrawStorageProtocol> drawStorage, CGRect rect))block
|
||
|
{
|
||
|
[_drawRectDictionary enumerateKeysAndObjectsUsingBlock:^(NSValue *rectValue, id<TYDrawStorageProtocol> drawStorage, BOOL * stop) {
|
||
|
if (block) {
|
||
|
block(drawStorage,[rectValue CGRectValue]);
|
||
|
}
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
- (BOOL)enumerateRunRectContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id<TYTextStorageProtocol> textStorage))successBlock
|
||
|
{
|
||
|
return [self enumerateRunRect:_runRectDictionary ContainPoint:point viewHeight:viewHeight successBlock:successBlock];
|
||
|
}
|
||
|
|
||
|
- (BOOL)enumerateLinkRectContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id<TYLinkStorageProtocol> textStorage))successBlock
|
||
|
{
|
||
|
return [self enumerateRunRect:_linkRectDictionary ContainPoint:point viewHeight:viewHeight successBlock:successBlock];
|
||
|
}
|
||
|
|
||
|
- (BOOL)enumerateRunRect:(NSDictionary *)runRectDic ContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id textStorage))successBlock
|
||
|
{
|
||
|
if (runRectDic.count == 0) {
|
||
|
return NO;
|
||
|
}
|
||
|
// CoreText context coordinates are the opposite to UIKit so we flip the bounds
|
||
|
CGAffineTransform transform = CGAffineTransformScale(CGAffineTransformMakeTranslation(0, viewHeight), 1.f, -1.f);
|
||
|
|
||
|
__block BOOL find = NO;
|
||
|
// 遍历run位置字典
|
||
|
[runRectDic enumerateKeysAndObjectsUsingBlock:^(NSValue *keyRectValue, id<TYTextStorageProtocol> textStorage, BOOL *stop) {
|
||
|
|
||
|
CGRect imgRect = [keyRectValue CGRectValue];
|
||
|
CGRect rect = CGRectApplyAffineTransform(imgRect, transform);
|
||
|
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol) ]) {
|
||
|
rect = UIEdgeInsetsInsetRect(rect,((id<TYDrawStorageProtocol>)textStorage).margin);
|
||
|
}
|
||
|
|
||
|
// point 是否在rect里
|
||
|
if(CGRectContainsPoint(rect, point)){
|
||
|
find = YES;
|
||
|
*stop = YES;
|
||
|
if (successBlock) {
|
||
|
successBlock(textStorage);
|
||
|
}
|
||
|
}
|
||
|
}];
|
||
|
return find;
|
||
|
}
|
||
|
|
||
|
- (void)dealloc{
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
#pragma mark - add textStorage
|
||
|
@implementation TYTextContainer (Add)
|
||
|
|
||
|
- (void)addTextStorage:(id<TYTextStorageProtocol>)textStorage
|
||
|
{
|
||
|
if (textStorage) {
|
||
|
[self.textStorageArray addObject:textStorage];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)addTextStorageArray:(NSArray *)textStorageArray
|
||
|
{
|
||
|
if (textStorageArray) {
|
||
|
for (id<TYTextStorageProtocol> textStorage in textStorageArray) {
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYTextStorageProtocol)]) {
|
||
|
[self addTextStorage:textStorage];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
@end
|
||
|
|
||
|
#pragma mark - append textStorage
|
||
|
@implementation TYTextContainer (Append)
|
||
|
|
||
|
- (void)appendText:(NSString *)text
|
||
|
{
|
||
|
NSAttributedString *attributedText = [self createTextAttibuteStringWithText:text];
|
||
|
[self appendTextAttributedString:attributedText];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
|
||
|
- (void)appendTextAttributedString:(NSAttributedString *)attributedText
|
||
|
{
|
||
|
if (attributedText == nil) {
|
||
|
return;
|
||
|
}
|
||
|
if (_attString == nil) {
|
||
|
_attString = [[NSMutableAttributedString alloc] init];
|
||
|
}
|
||
|
|
||
|
if ([attributedText isKindOfClass:[NSMutableAttributedString class]]) {
|
||
|
[self addTextParaphStyleWithAtrributedString:(NSMutableAttributedString *)attributedText];
|
||
|
}
|
||
|
|
||
|
[_attString appendAttributedString:attributedText];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
|
||
|
- (void)appendTextStorage:(id<TYAppendTextStorageProtocol>)textStorage
|
||
|
{
|
||
|
if (textStorage) {
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol)]) {
|
||
|
[(id<TYDrawStorageProtocol>)textStorage setTextfontAscent:_font.ascender descent:_font.descender];
|
||
|
} else if ([textStorage conformsToProtocol:@protocol(TYLinkStorageProtocol)]) {
|
||
|
if (!((id<TYLinkStorageProtocol>)textStorage).textColor) {
|
||
|
((id<TYLinkStorageProtocol>)textStorage).textColor = _linkColor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NSAttributedString *attAppendString = [textStorage appendTextStorageAttributedString];
|
||
|
textStorage.realRange = NSMakeRange(_attString.length, attAppendString.length);
|
||
|
[self appendTextAttributedString:attAppendString];
|
||
|
[self resetFrameRef];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)appendTextStorageArray:(NSArray *)textStorageArray
|
||
|
{
|
||
|
if (textStorageArray) {
|
||
|
for (id<TYAppendTextStorageProtocol> textStorage in textStorageArray) {
|
||
|
if ([textStorage conformsToProtocol:@protocol(TYAppendTextStorageProtocol)]) {
|
||
|
[self appendTextStorage:textStorage];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@end
|