1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
| #pragma mark - 一行一行绘制,行高确定,高度不够时加上省略号
- (void)drawRectWithLineByLineAlignmentAndEllipses
{
// 1.创建需要绘制的文字
NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithString:self.text];
// 2.设置行距等样式
[[self class] addGlobalAttributeWithContent:attributed font:self.font];
self.textHeight = [[self class] textHeightWithText:self.text width:CGRectGetWidth(self.bounds) font:self.font type:self.drawType];
// 3.创建绘制区域,path的高度对绘制有直接影响,如果高度不够,则计算出来的CTLine的数量会少一行或者少多行
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, CGRectGetWidth(self.bounds), self.textHeight*2));
// 4.根据NSAttributedString生成CTFramesetterRef
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributed);
CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, NULL);
// 重置高度
CGFloat realHeight = self.textHeight;
// 绘制全部文本需要的高度大于实际高度则调整,并加上省略号
if (realHeight > CGRectGetHeight(self.frame))
{
realHeight = CGRectGetHeight(self.frame);
}
NSLog(@"realHeight = %f",realHeight);
// 获取上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// 转换坐标系
CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
CGContextTranslateCTM(contextRef, 0, realHeight); // 这里跟着调整
CGContextScaleCTM(contextRef, 1.0, -1.0);
// 这里可调整可不调整
CGPathAddRect(path, NULL, CGRectMake(0, 0, CGRectGetWidth(self.bounds), realHeight));
// 一行一行绘制
CFArrayRef lines = CTFrameGetLines(ctFrame);
CFIndex lineCount = CFArrayGetCount(lines);
CGPoint lineOrigins[lineCount];
// 把ctFrame里每一行的初始坐标写到数组里,注意CoreText的坐标是左下角为原点
CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
CGFloat frameY = 0;
for (CFIndex i = 0; i < lineCount; i++)
{
// 遍历每一行CTLine
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGFloat lineAscent;
CGFloat lineDescent;
CGFloat lineLeading; // 行距
// 该函数除了会设置好ascent,descent,leading之外,还会返回这行的宽度
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
CGPoint lineOrigin = lineOrigins[i];
// 微调Y值,需要注意的是CoreText的origin的Y值是在baseLine处,而不是下方的descent。
CGFloat lineHeight = self.font.pointSize * kPerLineRatio;
// 调节self.font.descender该值可改变文字排版的上下间距,此处下间距为0
frameY = realHeight - (i + 1)*lineHeight - self.font.descender;
NSLog(@"frameY = %f",frameY);
lineOrigin.y = frameY;
// 调整坐标
CGContextSetTextPosition(contextRef, lineOrigin.x, lineOrigin.y);
// 反转坐标系
frameY = realHeight - frameY;
NSLog(@"realHeight = %f,font.descender = %f",realHeight,self.font.descender);
NSLog(@"反转后的坐标 y = %f",frameY);
// 行高
CGFloat heightPerLine = self.font.pointSize * kPerLineRatio;
if (realHeight - frameY > heightPerLine)
{
CTLineDraw(line, contextRef);
NSLog(@"一行一行的画 i = %ld",i);
} else
{
NSLog(@"最后一行");
// 最后一行,加上省略号
static NSString* const kEllipsesCharacter = @"\u2026";
CFRange lastLineRange = CTLineGetStringRange(line);
// 一个emoji表情占用两个长度单位
NSLog(@"range.location = %ld,range.length = %ld,总长度 = %ld",lastLineRange.location,lastLineRange.length,attributed.length);
if (lastLineRange.location + lastLineRange.length < (CFIndex)attributed.length)
{
// 这一行放不下所有的字符(下一行还有字符),则把此行后面的回车、空格符去掉后,再把最后一个字符替换成省略号
CTLineTruncationType truncationType = kCTLineTruncationEnd;
NSUInteger truncationAttributePosition = lastLineRange.location + lastLineRange.length - 1;
// 拿到最后一个字符的属性字典
NSDictionary *tokenAttributes = [attributed attributesAtIndex:truncationAttributePosition
effectiveRange:NULL];
// 给省略号字符设置字体大小、颜色等属性
NSAttributedString *tokenString = [[NSAttributedString alloc] initWithString:kEllipsesCharacter
attributes:tokenAttributes];
// 用省略号单独创建一个CTLine,下面在截断重新生成CTLine的时候会用到
CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)tokenString);
// 把这一行的属性字符串复制一份,如果要把省略号放到中间或其他位置,只需指定复制的长度即可
NSUInteger copyLength = lastLineRange.length/3;
NSMutableAttributedString *truncationString = [[attributed attributedSubstringFromRange:NSMakeRange(lastLineRange.location, copyLength)] mutableCopy];
if (lastLineRange.length > 0)
{
// Remove any whitespace at the end of the line.
unichar lastCharacter = [[truncationString string] characterAtIndex:copyLength - 1];
// 如果复制字符串的最后一个字符是换行、空格符,则删掉
if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:lastCharacter])
{
[truncationString deleteCharactersInRange:NSMakeRange(copyLength - 1, 1)];
}
}
// 拼接省略号到复制字符串的最后
[truncationString appendAttributedString:tokenString];
// 把新的字符串创建成CTLine
CTLineRef truncationLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncationString);
// 创建一个截断的CTLine,该方法不能少,具体作用还有待研究
CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, self.frame.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, contextRef);
CFRelease(truncatedLine);
} else
{
// 这一行刚好是最后一行,且最后一行的字符可以完全绘制出来
CTLineDraw(line, contextRef);
}
// 跳出循环,避免绘制剩下的多余的CTLine
break;
}
}
CFRelease(path);
CFRelease(framesetter);
CFRelease(ctFrame);
}
|