项目需要一个环形进度条,所以需要弧形的文字显示进度,网上找了一圈没有什么适合的,所以自己封装了一个简单的弧形文字。
效果图如下:
首先我们需要一个结构体来计算出每个glyph的绘制点和偏移角度:
typedef struct GlyphArcInfo {
CGFloat width;
CGFloat angle;
} GlyphArcInfo;
创建上面结构体并赋值:
CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)self.attString);
assert(line != NULL); // 创建排版
CFIndex glyphCount = CTLineGetGlyphCount(line);
if (glyphCount == 0) { // 获取字符总数
CFRelease(line);
return;
}
GlyphArcInfo *glyphArcInfo = (GlyphArcInfo *)calloc(glyphCount, sizeof(GlyphArcInfo)); // 创建GlyphArcInfo对象数组 GlyphArcInfo包含width和angle
PrepareGlyphArcInfo(line, glyphCount, glyphArcInfo, endAngle - startAngle);
其中PrepareGlyphArcInfo()是赋值的函数,它通过遍历每个CTRun中的glyph,并用单个glyph的width对结构体GlyphArcInfo赋值,并通过每个glyph中心点距离和总行宽的比值计算angle进行赋值:
static void PrepareGlyphArcInfo(CTLineRef line, CFIndex glyphCount, GlyphArcInfo *glyphArcInfo, CGFloat spaceAngle)
{
spaceAngle = -spaceAngle;
NSArray *runArray = (__bridge NSArray *)CTLineGetGlyphRuns(line); // 获取CTRun(字符模块)数组
// Examine each run in the line, updating glyphOffset to track how far along the run is in terms of glyphCount.
CFIndex glyphOffset = 0; // 记录glyphArcInfo数组的当前下标
for (id run in runArray) { // 遍历CTRun的对象数组
CFIndex runGlyphCount = CTRunGetGlyphCount((__bridge CTRunRef)run); // 获取glyph(字形)的数量
// Ask for the width of each glyph in turn.
CFIndex runGlyphIndex = 0;
for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) {
glyphArcInfo[runGlyphIndex + glyphOffset].width = CTRunGetTypographicBounds((__bridge CTRunRef)run, CFRangeMake(runGlyphIndex, 1), NULL, NULL, NULL); // 对glyphArcInfo数组中的元素赋值width = run中glyph的bounding box的width
}
glyphOffset += runGlyphCount;
}
double lineLength = CTLineGetTypographicBounds(line, NULL, NULL, NULL); // 计算排版边距
CGFloat prevHalfWidth = glyphArcInfo[0].width / 2.0;
glyphArcInfo[0].angle = (prevHalfWidth / lineLength) * spaceAngle; // 赋值角度 角度 = (首个glyph宽度 / 2.0 / 排版边距) * π
// Divide the arc into slices such that each one covers the distance from one glyph's center to the next.
CFIndex lineGlyphIndex = 1;
for (; lineGlyphIndex < glyphCount; lineGlyphIndex++) {
CGFloat halfWidth = glyphArcInfo[lineGlyphIndex].width / 2.0;
CGFloat prevCenterToCenter = prevHalfWidth + halfWidth;
glyphArcInfo[lineGlyphIndex].angle = (prevCenterToCenter / lineLength) * spaceAngle;
prevHalfWidth = halfWidth;
// prevCenterToCenter = 当前字符宽度 / 2.0 + 上一个字符宽度 / 2.0 即与上一个字符中心点的距离
}
}
核心实现部分:
- (void)drawRect:(CGRect)rect
{
// NSLog(@"...............%@", self.attString.string);
if (self.attString == nil) {
return;
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform t0 = CGContextGetCTM(context);
CGFloat xScaleFactor = t0.a > 0 ? t0.a : -t0.a;
CGFloat yScaleFactor = t0.d > 0 ? t0.d : -t0.d;
t0 = CGAffineTransformInvert(t0); // 反转t0
if (xScaleFactor != 1.0 || yScaleFactor != 1.0) {
t0 = CGAffineTransformScale(t0, xScaleFactor, yScaleFactor); // 修正缩放倍数
}
CGContextConcatCTM(context, t0);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// 翻转后角度也翻转
CGFloat startAngle = - self.startAngle;
CGFloat endAngle = - self.endAngle;
CGFloat positionY = self.radius;
CGFloat ctmAngle = - M_PI_2;
// Draw a white background
UIColor *backColor = self.backColor ? self.backColor : [UIColor whiteColor];
[backColor set];
UIRectFill(rect);
// CGContextSetFillColorWithColor(context, [UIColor cyanColor].CGColor);
// CGContextFillRect(context, self.layer.bounds);
CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)self.attString);
assert(line != NULL); // 创建排版
CFIndex glyphCount = CTLineGetGlyphCount(line);
if (glyphCount == 0) { // 获取字符总数
CFRelease(line);
return;
}
GlyphArcInfo *glyphArcInfo = (GlyphArcInfo *)calloc(glyphCount, sizeof(GlyphArcInfo)); // 创建GlyphArcInfo对象数组 GlyphArcInfo包含width和angle
PrepareGlyphArcInfo(line, glyphCount, glyphArcInfo, endAngle - startAngle);
// Move the origin from the lower left of the view nearer to its center.
CGContextSaveGState(context); // 压栈当前的绘制状态 用于保存这之前的图形状态 之后做的任何修改都不影响栈堆中的拷贝
CGContextTranslateCTM(context, self.contextCenter.x, self.contextCenter.y); // 将原点从视图的左下角移动到指定位置(坐标系向上为正)。
/* 画一个圆弧用作参照
// Stroke the arc in red for verification.
CGContextBeginPath(context);
// 绘制圆弧 参数依次为 context 中心点的x,中心点的y, 半径,第一个点角度,第二个点角度,是否顺时针
CGContextAddArc(context, 0.0, 0.0, self.radius, 0, 2 * M_PI, 1);
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); // 红色线条
CGContextStrokePath(context);
*/
// 开始绘制图形
// CGPoint textPosition = CGPointMake(0, - self.radius); // 绘制的起始坐标(向下为正) 由于进行了翻转 所以变成下面
CGPoint textPosition = CGPointMake(0, positionY);
CGContextSetTextPosition(context, textPosition.x, textPosition.y);
// CGContextRotateCTM(context, self.startAngle + M_PI_2); // 将上下文逆时针旋转 使字体起始点到准确位置。 由于进行了翻转 所以变成下面
CGContextRotateCTM(context, startAngle + ctmAngle);
CFArrayRef runArray = CTLineGetGlyphRuns(line);
CFIndex runCount = CFArrayGetCount(runArray);
CFIndex glyphOffset = 0;
CFIndex runIndex = 0;
for (; runIndex < runCount; runIndex++) {
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
CFIndex runGlyphCount = CTRunGetGlyphCount(run);
CFIndex runGlyphIndex = 0; // 记录glyphArcInfo下标
for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) {
CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);
CGContextRotateCTM(context, - (glyphArcInfo[runGlyphIndex + glyphOffset].angle)); // 旋转当前图形状态 调整每个glyph的位置
// Center this glyph by moving left by half its width.
CGFloat glyphWidth = glyphArcInfo[runGlyphIndex + glyphOffset].width;
CGFloat halfGlyphWidth = glyphWidth / 2.0;
CGPoint positionForThisGlyph = CGPointMake(textPosition.x - halfGlyphWidth, textPosition.y); // 当前绘制点往前半个字距的点
CGAffineTransform textMatrix = CTRunGetTextMatrix(run);
textMatrix.tx = positionForThisGlyph.x;
textMatrix.ty = positionForThisGlyph.y; // 设置当前文本位置
CGContextSetTextMatrix(context, textMatrix); // 相当于CGContextSetTextPosition(context, positionForThisGlyph.x, positionForThisGlyph.y); // 把绘制起点移到下一个字符的中心点
CTRunDraw(run, context, glyphRange);
textPosition.x -= glyphWidth; // 绘制点偏移当前字符间距
}
glyphOffset += runGlyphCount;
}
CGContextRestoreGState(context); // 一般与CGContextSaveGState成对出现 save将图形上下文推入栈顶,restore将图形上下文出栈 使上下文状态回到save前
free(glyphArcInfo);
CFRelease(line);
}
具体可以看https://www.bilibili.com/read/cv15417795
还有环形进度条https://www.bilibili.com/read/cv15418464
效果图:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)