现有圆圈上的曲线文本

Posted

技术标签:

【中文标题】现有圆圈上的曲线文本【英文标题】:Curve text on existing circle 【发布时间】:2011-04-19 23:46:22 【问题描述】:

对于我正在构建的应用程序,我画了 2 个圆圈。一个比另一个大一点。我想在这些行之间弯曲文本,用于我正在构建的圆形菜单。

我阅读了大多数关于弯曲文本的内容,您必须将文本拆分为字符,并以正确的角度单独绘制每个字符(通过旋转您正在绘制的上下文)。

我只是不知道如何为我的角色获得正确的角度和位置。

我附上了一张关于目前菜单外观的屏幕截图。只有我添加的文本是从 UIImageView 中的图像加载的。

我希望有人能给我一些关于如何在某些点绘制白色圆圈中的文本的起点。

编辑: 好的,我目前在这一点上:

我通过使用以下代码来完成:

- (UIImage*) createMenuRingWithFrame:(CGRect)frame

    CGRect imageSize = CGRectMake(0,0,300,300);
    float perSectionDegrees = 360 / [sections count];
    float totalRotation = 90;
    char* fontName = (char*)[self.menuItemsFont.fontName cStringUsingEncoding:NSASCIIStringEncoding];

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, imageSize.size.width, imageSize.size.height, 8, 4 * imageSize.size.width, colorSpace, kCGImageAlphaPremultipliedFirst);

    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextSelectFont(context, fontName, 18, kCGEncodingMacRoman);

    CGContextSetRGBFillColor(context, 0, 0, 0, 1);

    CGPoint centerPoint = CGPointMake(imageSize.size.width / 2, imageSize.size.height / 2);
    double radius = (frame.size.width / 2);

    CGContextStrokeEllipseInRect(context, CGRectMake(centerPoint.x - (frame.size.width / 2), centerPoint.y - (frame.size.height / 2), frame.size.width, frame.size.height));

    for (int index = 0; index < [sections count]; index++)
    
        NSString* menuItemText = [sections objectAtIndex:index];
        CGSize textSize = [menuItemText sizeWithFont:self.menuItemsFont];
        char* menuItemTextChar = (char*)[menuItemText cStringUsingEncoding:NSASCIIStringEncoding];

        float x = centerPoint.x + radius * cos(degreesToRadians(totalRotation));
        float y = centerPoint.y + radius * sin(degreesToRadians(totalRotation));

        CGContextSaveGState(context);

        CGContextTranslateCTM(context, x, y);
        CGContextRotateCTM(context, degreesToRadians(totalRotation - 90));
        CGContextShowTextAtPoint(context, 0 - (textSize.width / 2), 0 - (textSize.height / 2), menuItemTextChar, strlen(menuItemTextChar));

        CGContextRestoreGState(context);

        totalRotation += perSectionDegrees;
    

    CGImageRef contextImage = CGBitmapContextCreateImage(context);

    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    return [UIImage imageWithCGImage:contextImage];

这些是我在那里使用的变量:

NSArray* sections = [[NSArray alloc] initWithObjects:@"settings", @"test", @"stats", @"nog iets", @"woei", @"woei2", nil];
self.menuItemsFont = [UIFont fontWithName:@"VAGRounded-Bold" size:18];

单词的旋转似乎正确,位置也正确。现在我需要以某种方式弄清楚字母(及其坐标)应该在哪个旋转位置。我可以使用一些帮助。

编辑:已修复!查看以下代码!

- (void) drawStringAtContext:(CGContextRef) context string:(NSString*) text atAngle:(float) angle withRadius:(float) radius

    CGSize textSize = [text sizeWithFont:self.menuItemsFont];

    float perimeter = 2 * M_PI * radius;
    float textAngle = textSize.width / perimeter * 2 * M_PI;

    angle += textAngle / 2;

    for (int index = 0; index < [text length]; index++)
    
        NSRange range = index, 1;
        NSString* letter = [text substringWithRange:range];     
        char* c = (char*)[letter cStringUsingEncoding:NSASCIIStringEncoding];
        CGSize charSize = [letter sizeWithFont:self.menuItemsFont];

        NSLog(@"Char %@ with size: %f x %f", letter, charSize.width, charSize.height);

        float x = radius * cos(angle);
        float y = radius * sin(angle);

        float letterAngle = (charSize.width / perimeter * -2 * M_PI);

        CGContextSaveGState(context);
        CGContextTranslateCTM(context, x, y);
        CGContextRotateCTM(context, (angle - 0.5 * M_PI));
        CGContextShowTextAtPoint(context, 0, 0, c, strlen(c));
        CGContextRestoreGState(context);

        angle += letterAngle;
    


- (UIImage*) createMenuRingWithFrame:(CGRect)frame

    CGPoint centerPoint = CGPointMake(frame.size.width / 2, frame.size.height / 2);
    char* fontName = (char*)[self.menuItemsFont.fontName cStringUsingEncoding:NSASCIIStringEncoding];

    CGFloat* ringColorComponents = (float*)CGColorGetComponents(ringColor.CGColor);
    CGFloat* textColorComponents = (float*)CGColorGetComponents(textColor.CGColor);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, frame.size.width, frame.size.height, 8, 4 * frame.size.width, colorSpace, kCGImageAlphaPremultipliedFirst);

    CGContextSetTextMatrix(context, CGAffineTransformIdentity);

    CGContextSelectFont(context, fontName, 18, kCGEncodingMacRoman);
    CGContextSetRGBStrokeColor(context, ringColorComponents[0], ringColorComponents[1], ringColorComponents[2], ringAlpha);
    CGContextSetLineWidth(context, ringWidth);  

    CGContextStrokeEllipseInRect(context, CGRectMake(ringWidth, ringWidth, frame.size.width - (ringWidth * 2), frame.size.height - (ringWidth * 2)));
    CGContextSetRGBFillColor(context, textColorComponents[0], textColorComponents[1], textColorComponents[2], textAlpha);

    CGContextSaveGState(context);
    CGContextTranslateCTM(context, centerPoint.x, centerPoint.y);

    float angleStep = 2 * M_PI / [sections count];
    float angle = degreesToRadians(90);

    textRadius = textRadius - 12;

    for (NSString* text in sections)
    
        [self drawStringAtContext:context string:text atAngle:angle withRadius:textRadius];
        angle -= angleStep;
    

    CGContextRestoreGState(context);

    CGImageRef contextImage = CGBitmapContextCreateImage(context);

    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    [self saveImage:[UIImage imageWithCGImage:contextImage] withName:@"test.png"];
    return [UIImage imageWithCGImage:contextImage];


【问题讨论】:

这种方法对我很有效——直到我不得不使用特殊字符,如度数符号和外国变音符号。这些字符是我使用的字体,所以我猜这是编码问题?更改 NSASCIIStringEncoding 并没有解决我的问题,所以我选择了 CoreText 解决方案(见下文)。 我厌倦了使用此代码,但遇到了一些问题,即使使用更新后的帖子.. 例如,textRadius 错误为未定义。我猜它是一个浮点数,但不确定它应该从什么开始。我试图画一个单一的字符串,所以我最终没有使用它。 ios7现在也弃用了许多功能。如果您尝试使用它并且在屏幕上看不到任何文本,请尝试使用其他字体。显然 VAGRunded-Bold 不是标准的 IOS 字体。 【参考方案1】:

这是我在图层上以预定义的角度(以弧度为单位)绘制曲线属性字符串的方法:

[self drawCurvedStringOnLayer:self.layer withAttributedText:incident atAngle:angle withRadius:300];

字符串在圆弧的底部区域也会自动反转。

- (void)drawCurvedStringOnLayer:(CALayer *)layer
             withAttributedText:(NSAttributedString *)text
                        atAngle:(float)angle
                     withRadius:(float)radius 

    // angle in radians

    CGSize textSize = CGRectIntegral([text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
                                                        options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                        context:nil]).size;

    float perimeter = 2 * M_PI * radius;
    float textAngle = (textSize.width / perimeter * 2 * M_PI); 

    float textRotation;
    float textDirection;
    if (angle > degreesToRadians(10) && angle < degreesToRadians(170)) 
        //bottom string
        textRotation = 0.5 * M_PI ;
        textDirection = - 2 * M_PI;
        angle += textAngle / 2;
     else 
        //top string
        textRotation = 1.5 * M_PI ;
        textDirection = 2 * M_PI;
        angle -= textAngle / 2;
    

    for (int c = 0; c < text.length; c++) 
        NSRange range = c, 1;
        NSAttributedString* letter = [text attributedSubstringFromRange:range];
        CGSize charSize = CGRectIntegral([letter boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
                                                              options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                              context:nil]).size;

        float letterAngle = ( (charSize.width / perimeter) * textDirection );

        float x = radius * cos(angle + (letterAngle/2));
        float y = radius * sin(angle + (letterAngle/2));

        CATextLayer *singleChar = [self drawTextOnLayer:layer
                                           withText:letter
                                              frame:CGRectMake(layer.frame.size.width/2 - charSize.width/2 + x,
                                                               layer.frame.size.height/2 - charSize.height/2 + y,
                                                               charSize.width, charSize.height)
                                            bgColor:nil
                                            opacity:1];

        singleChar.transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeRotation(angle - textRotation) );

        angle += letterAngle;
    



- (CATextLayer *)drawTextOnLayer:(CALayer *)layer
                        withText:(NSAttributedString *)text
                           frame:(CGRect)frame
                         bgColor:(UIColor *)bgColor
                         opacity:(float)opacity 

    CATextLayer *textLayer = [[CATextLayer alloc] init];
    [textLayer setFrame:frame];
    [textLayer setString:text];
    [textLayer setAlignmentMode:kCAAlignmentCenter];
    [textLayer setBackgroundColor:bgColor.CGColor];
    [textLayer setContentsScale:[UIScreen mainScreen].scale];
    [textLayer setOpacity:opacity];
    [layer addSublayer:textLayer];
    return textLayer;



/** Degrees to Radian **/
#define degreesToRadians(degrees) (( degrees ) / 180.0 * M_PI )

/** Radians to Degrees **/
#define radiansToDegrees(radians) (( radians ) * ( 180.0 / M_PI ) )

【讨论】:

这是一个很棒的解决方案! swift 3 有一些变化,主要是一切都应该是 CGFloat 并且drawTextOnLayer 中的 UIColor 应该是可选的,否则转换是微不足道的。 您好,感谢您的代码!我在这里做了一个 swift 3 端口gist.github.com/cemolcay/9525d28a203da83d333545fc6e0d4371【参考方案2】:

that 是最好的 url https://github.com/javenisme/CurvaView 来设置你的文本曲线:

但是根据度数曲线,我只需更新一点代码,我们可以将曲线设置为度数。比如 45,60,90 180, 360。

看代码:https://github.com/tikamsingh/CurveTextWithAngle

你可以考虑一下。

【讨论】:

是的,我提到了你的网址。但是,如果您查看代码,那么您就会明白我对此进行了哪些修改。我同意使用你的框架。但这只是一点点增强。【参考方案3】:

为了节省您的时间, 这是我为暴露的 CoreTextArcView 找到的

- (id)initWithFrame:(CGRect)frame font:(UIFont *)font text:(NSString *)text radius:(float)radius arcSize:(float)arcSize color:(UIColor *)color;
 (x,y)
      +-------------------------------------------------- -+
     ^| |  x x |
     || x (xc,yc) x |
     || x 

这对 r > 0 和 arcsize > 0 有效。

【讨论】:

【参考方案4】:

#import <Cocoa/Cocoa.h>

@interface CircleTextCell : NSCell 



@end

#import "CircleTextCell.h"

#define PI (3.141592653589793)

@implementation CircleTextCell

- (void)drawWithFrame: (NSRect)cellFrame inView: (NSView*)controlView

    NSAttributedString *str = [self attributedStringValue];
    NSSize stringSize = [str size];
    NSUInteger chars = [[str string] length];
    CGFloat radius = (stringSize.width + 5 * chars) / (2 * PI);
    CGFloat diameter = 2*radius;
    NSPoint scale = 1,1;
    if (diameter > cellFrame.size.width)
    
        scale.x = cellFrame.size.width / diameter;
    
    if (diameter > cellFrame.size.height)
    
        scale.y = cellFrame.size.height / diameter;
    
    NSAffineTransform *transform = [NSAffineTransform transform];
    NSAffineTransformStruct identity = [transform transformStruct];
    [transform scaleXBy: scale.x yBy: scale.y];
    [transform translateXBy: radius yBy: 0];
    [NSGraphicsContext saveGraphicsState];

    [transform concat];

    NSPoint origin = 0,0;
    CGFloat angleScale = 360 / (stringSize.width + (5 * chars));
    for (NSUInteger i=0 ; i<chars ; i++)
    
        NSAttributedString *substr = 
            [str attributedSubstringFromRange: NSMakeRange(i, 1)];
        [substr drawAtPoint: origin];
        [transform setTransformStruct: identity];
        CGFloat displacement = [substr size].width + 5;
        [transform translateXBy: displacement yBy: 0];
        [transform rotateByDegrees: angleScale * displacement];
        [transform concat];
    
    [NSGraphicsContext restoreGraphicsState];

@end

#import <Cocoa/Cocoa.h>

@class CircleTextCell;
@interface CircleTextView : NSView 
    CircleTextCell *cell;


@end

#import "CircleTextView.h"
#import "CircleTextCell.h"

@implementation CircleTextView
- (void)awakeFromNib

    NSDictionary *attributes = 
        [NSDictionary dictionaryWithObject: [NSFont fontWithName: @"Zapfino"
                                                            size:32]
                                    forKey: NSFontAttributeName];
    NSAttributedString *str =
        [[NSAttributedString alloc] initWithString: @"Hello World!  This is a very long text string that will be wrapped into a circle by a cell drawn in a custom view"
                                        attributes: attributes];
    cell = [[CircleTextCell alloc] init];
    [cell setAttributedStringValue: str];

- (void)drawRect:(NSRect)rect 

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect: rect];
    [cell drawWithFrame: [self bounds] inView: self];


@end

【讨论】:

【参考方案5】:

参考Ali Seyman's answer:

您可以下载使用 CoreTextArcView 的示例项目:https://github.com/javenisme/CurvaView

添加此方法以减小视图框架大小,就像 UILabel 一样。

- (void)sizeToFit
[super sizeToFit];

CGFloat width = ceilf( fabsf((self.radius*2)) + self.font.lineHeight) + 3.0;
CGRect f = self.frame;
f.size = CGSizeMake(width,width);
self.frame = f;
[self setNeedsDisplay];

如果有人在降低高度方面也有改进,欢迎补充。

【讨论】:

【参考方案6】:

我尝试了上面提到的git项目,正如ZpaceZombor所说,有一个错误的偏移量

CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV - self.radius / 2.0);

我只是改成了

CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV);

我已将半径设置为容器视图的宽度和高度之间的最小值,因此我已将圆弧大小设置为

我随意改了行

CGContextRotateCTM(context, _arcSize/2.0);

CGContextRotateCTM(context, M_PI_2);

我已将 init 方法更改为

- (id)initWithFrame:(CGRect)frame font:(UIFont *)font text:(NSString *)text color:(UIColor *)color

    self = [super initWithFrame:frame];
    if (self) 
        self.font = font;
        self.text = text;
        self.radius = -1 * (frame.size.width > frame.size.height ? frame.size.height / 2 : frame.size.width / 2);
        _arcSize = 2* M_PI;
        self.showsGlyphBounds = NO;
        self.showsLineMetrics = NO;
        self.dimsSubstitutedGlyphs = NO;
        self.color = color;
        self.shiftH = self.shiftV = 0.0f;

    
    return self;

经过多次尝试,我对函数PrepareGlyphArcInfo进行了修改

// this constants come from a single case ( fontSize = 22 | circle diameter = 250px | lower circle diameter 50px | 0.12f is a proportional acceptable value of 250px diameter | 0.18f is a proportional acceptable value of 50px | 0.035f is a proportional acceptable value of "big" chars
#define kReferredCharSpacing 0.12f
#define kReferredFontSize 22.f
#define kReferredMajorDiameter 250.f
#define kReferredMinorDiameter 50.f
#define kReferredMinorSpacingFix 0.18f
#define kReferredBigCharSpacingFix  0.035f

static void PrepareGlyphArcInfo(UIFont* font,CGFloat containerRadius,CTLineRef line, CFIndex glyphCount, GlyphArcInfo *glyphArcInfo, CGFloat arcSizeRad)

    NSArray *runArray = (NSArray *)CTLineGetGlyphRuns(line);

    CGFloat curMaxTypoWidth = 0.f;
    CGFloat curMinTypoWidth = 0.f;

    // Examine each run in the line, updating glyphOffset to track how far along the run is in terms of glyphCount.
    CFIndex glyphOffset = 0;
    for (id run in runArray) 
        CFIndex runGlyphCount = CTRunGetGlyphCount((CTRunRef)run);

            // Ask for the width of each glyph in turn.
        CFIndex runGlyphIndex = 0;
        for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) 
            glyphArcInfo[runGlyphIndex + glyphOffset].width = CTRunGetTypographicBounds((CTRunRef)run, CFRangeMake(runGlyphIndex, 1), NULL, NULL, NULL);

            if (curMaxTypoWidth < glyphArcInfo[runGlyphIndex + glyphOffset].width)
                curMaxTypoWidth = glyphArcInfo[runGlyphIndex + glyphOffset].width;

            if (curMinTypoWidth > glyphArcInfo[runGlyphIndex + glyphOffset].width || curMinTypoWidth == 0)
                curMinTypoWidth = glyphArcInfo[runGlyphIndex + glyphOffset].width;

        

        glyphOffset += runGlyphCount;
    

    //double lineLength = CTLineGetTypographicBounds(line, NULL, NULL, NULL);

    glyphArcInfo[0].angle = M_PI_2; // start at the bottom circle

    CFIndex lineGlyphIndex = 1;

    // based on font size. (supposing that with fontSize = 22 we could use 0.12)
    CGFloat maxCharSpacing = font.pointSize * kReferredCharSpacing / kReferredFontSize;

    // for diameter minor than referred 250
    if ((fabsf(containerRadius)*2) < kReferredMajorDiameter)
        maxCharSpacing = maxCharSpacing + kReferredMinorSpacingFix * kReferredMinorDiameter / (fabsf(containerRadius)*2);

    CGFloat startAngle = fabsf(glyphArcInfo[0].angle);
    CGFloat endAngle = startAngle;

    for (; lineGlyphIndex < glyphCount; lineGlyphIndex++) 

        CGFloat deltaWidth = curMaxTypoWidth - glyphArcInfo[lineGlyphIndex].width;

        // fix applied to large characters like uppercase letters or symbols
        CGFloat bigCharFix = (glyphArcInfo[lineGlyphIndex-1].width == curMaxTypoWidth || (glyphArcInfo[lineGlyphIndex-1].width+2) >= curMaxTypoWidth ? kReferredBigCharSpacingFix : 0 );

        glyphArcInfo[lineGlyphIndex].angle = - (maxCharSpacing * (glyphArcInfo[lineGlyphIndex].width + deltaWidth ) / curMaxTypoWidth) - bigCharFix;

        endAngle += fabsf(glyphArcInfo[lineGlyphIndex].angle);
    

    // center text to bottom
    glyphArcInfo[0].angle = glyphArcInfo[0].angle + (endAngle - startAngle ) / 2;


并将drawRect: 方法更改为

- (void)drawRect:(CGRect)rect 
    // Don't draw if we don't have a font or string
    if (self.font == NULL || self.text == NULL) 
        return;

    // Initialize the text matrix to a known value
    CGContextRef context = UIGraphicsGetCurrentContext();

    //Reset the transformation
    //Doing this means you have to reset the contentScaleFactor to 1.0
    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);
    if (xScaleFactor != 1.0 || yScaleFactor != 1.0)
        t0 = CGAffineTransformScale(t0, xScaleFactor, yScaleFactor);

    CGContextConcatCTM(context, t0);

    CGContextSetTextMatrix(context, CGAffineTransformIdentity);

    NSAttributedString *attStr = self.attributedString;
    CFAttributedStringRef asr = (CFAttributedStringRef)attStr;
    CTLineRef line = CTLineCreateWithAttributedString(asr);
    assert(line != NULL);

    CFIndex glyphCount = CTLineGetGlyphCount(line);
    if (glyphCount == 0) 
        CFRelease(line);
        return;
    

    GlyphArcInfo *  glyphArcInfo = (GlyphArcInfo*)calloc(glyphCount, sizeof(GlyphArcInfo));
    PrepareGlyphArcInfo(self.font, self.radius, line, glyphCount, glyphArcInfo, _arcSize);

    // Move the origin from the lower left of the view nearer to its center.
    CGContextSaveGState(context);

    CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV);

    if(ARCVIEW_DEBUG_MODE)
        // Stroke the arc in red for verification.
        CGContextBeginPath(context);
        CGContextAddArc(context, 0.0, 0.0, self.radius, M_PI_2+_arcSize/2.0, M_PI_2-_arcSize/2.0, 1);
        CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
        CGContextStrokePath(context);
    

    // Rotate the context 90 degrees counterclockwise (per 180 degrees)
    CGContextRotateCTM(context, M_PI_2);

    // Now for the actual drawing. The angle offset for each glyph relative to the previous glyph has already been calculated; with that information in hand, draw those glyphs overstruck and centered over one another, making sure to rotate the context after each glyph so the glyphs are spread along a semicircular path.

    CGPoint textPosition = CGPointMake(0.0, self.radius);
    CGContextSetTextPosition(context, textPosition.x, textPosition.y);

    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);
        Boolean drawSubstitutedGlyphsManually = false;
        CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);

        // Determine if we need to draw substituted glyphs manually. Do so if the runFont is not the same as the overall font.
        if (self.dimsSubstitutedGlyphs && ![self.font isEqual:(UIFont *)runFont]) 
            drawSubstitutedGlyphsManually = true;
        

        CFIndex runGlyphIndex = 0;
        for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) 
            CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);
            CGContextRotateCTM(context, -(glyphArcInfo[runGlyphIndex + glyphOffset].angle));

            // 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);

            // Glyphs are positioned relative to the text position for the line, so offset text position leftwards by this glyph's width in preparation for the next glyph.
            textPosition.x -= glyphWidth;

            CGAffineTransform textMatrix = CTRunGetTextMatrix(run);
            textMatrix.tx = positionForThisGlyph.x;
            textMatrix.ty = positionForThisGlyph.y;
            CGContextSetTextMatrix(context, textMatrix);

            CTRunDraw(run, context, glyphRange);
        

        glyphOffset += runGlyphCount;
    

    CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
    CGContextSetAlpha(context,0.0);
    CGContextFillRect(context, rect);

    CGContextRestoreGState(context);

    free(glyphArcInfo);
    CFRelease(line);    


如您所见,我使用 真的不好 方法来计算每个字符之间的间距(在原始示例中,字符之间的间距也基于弧线大小)。无论如何,这似乎几乎可以正常工作。

最好的解决方案可能是弯曲一个矩形(所以是线性文本),图形工作量和奇怪的计算更少。

这是我得到的

希望对你有帮助

【讨论】:

我从这里使用了你的一些代码来控制***.com/questions/29819787/… 现在我想在每个单词之间设置相等的空间。有人可以帮我做吗?【参考方案7】:

您可以下载使用 CoreTextArcView 的示例项目:https://github.com/javenisme/CurvaView

【讨论】:

这一款效果很好。只需转换为符合我需要的 ARC 即可。 按要求最好【参考方案8】:

我改编了 Apple 的 CoreTextArcCocoa 示例项目(Tom H 在this reply 中提到)并想在这里分享。

我还添加了一些其他功能,例如将圆弧大小设置为小于 180 的能力,以及作为属性的文本颜色和偏移偏移(这样您就不必有一个巨大的框架来显示全文)。

 /*

 File: CoreTextArcView.m (iOS version)

 Abstract: Defines and implements the CoreTextArcView custom UIView subclass to
 draw text on a curve and illustrate best practices with CoreText.

 Based on CoreTextArcView provided by Apple for Mac OS X https://developer.apple.com/library/mac/#samplecode/CoreTextArcCocoa/Introduction/Intro.html

 Ported to iOS (& added color, arcsize features) August 2011 by Alec Vance, Juggleware LLC http://juggleware.com/

 */ 

#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>


@interface CoreTextArcView : UIView 
@private
    UIFont *            _font;
    NSString *          _string;
    CGFloat             _radius;
    UIColor *           _color;
    CGFloat             _arcSize;
    CGFloat             _shiftH, _shiftV; // horiz & vertical shift

    struct 
        unsigned int    showsGlyphBounds:1;
        unsigned int    showsLineMetrics:1;
        unsigned int    dimsSubstitutedGlyphs:1;
        unsigned int    reserved:29;
                       _flags;


@property(retain, nonatomic) UIFont *font;
@property(retain, nonatomic) NSString *text;
@property(readonly, nonatomic) NSAttributedString *attributedString;
@property(assign, nonatomic) CGFloat radius;
@property(nonatomic) BOOL showsGlyphBounds;
@property(nonatomic) BOOL showsLineMetrics;
@property(nonatomic) BOOL dimsSubstitutedGlyphs;
@property(retain, nonatomic) UIColor *color;
@property(nonatomic) CGFloat arcSize;
@property(nonatomic) CGFloat shiftH, shiftV;
@end


/*

 File: CoreTextArcView.m (iOS version)

 */ 

#import "CoreTextArcView.h"
#import <AssertMacros.h>
#import <QuartzCore/QuartzCore.h>

#define ARCVIEW_DEBUG_MODE          NO

#define ARCVIEW_DEFAULT_FONT_NAME   @"Helvetica"
#define ARCVIEW_DEFAULT_FONT_SIZE   64.0
#define ARCVIEW_DEFAULT_RADIUS      150.0
#define ARCVIEW_DEFAULT_ARC_SIZE    180.0



@implementation CoreTextArcView

- (id)initWithFrame:(CGRect)frame 
    self = [super initWithFrame:frame];
    if (self) 
        self.font = [UIFont fontWithName:ARCVIEW_DEFAULT_FONT_NAME size:ARCVIEW_DEFAULT_FONT_SIZE];
        self.text = @"Curvaceous Type";
        self.radius = ARCVIEW_DEFAULT_RADIUS;
        self.showsGlyphBounds = NO;
        self.showsLineMetrics = NO;
        self.dimsSubstitutedGlyphs = NO;
        self.color = [UIColor whiteColor];
        self.arcSize = ARCVIEW_DEFAULT_ARC_SIZE;
        self.shiftH = self.shiftV = 0.0f;
    
    return self;


typedef struct GlyphArcInfo 
    CGFloat         width;
    CGFloat         angle;  // in radians
 GlyphArcInfo;

static void PrepareGlyphArcInfo(CTLineRef line, CFIndex glyphCount, GlyphArcInfo *glyphArcInfo, CGFloat arcSizeRad)

    NSArray *runArray = (NSArray *)CTLineGetGlyphRuns(line);

    // Examine each run in the line, updating glyphOffset to track how far along the run is in terms of glyphCount.
    CFIndex glyphOffset = 0;
    for (id run in runArray) 
        CFIndex runGlyphCount = CTRunGetGlyphCount((CTRunRef)run);

        // Ask for the width of each glyph in turn.
        CFIndex runGlyphIndex = 0;
        for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) 
            glyphArcInfo[runGlyphIndex + glyphOffset].width = CTRunGetTypographicBounds((CTRunRef)run, CFRangeMake(runGlyphIndex, 1), NULL, NULL, NULL);
        

        glyphOffset += runGlyphCount;
    

    double lineLength = CTLineGetTypographicBounds(line, NULL, NULL, NULL);

    CGFloat prevHalfWidth = glyphArcInfo[0].width / 2.0;
    glyphArcInfo[0].angle = (prevHalfWidth / lineLength) * arcSizeRad;

    // 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) * arcSizeRad;

        prevHalfWidth = halfWidth;
    



// ensure that redraw occurs.
-(void)setText:(NSString *)text
    [_string release];
    _string = [text retain];

    [self setNeedsDisplay];


//set arc size in degrees (180 = half circle)
-(void)setArcSize:(CGFloat)degrees
    _arcSize = degrees * M_PI/180.0;


//get arc size in degrees
-(CGFloat)arcSize
    return _arcSize * 180.0/M_PI;


- (void)drawRect:(CGRect)rect 
    // Don't draw if we don't have a font or string
    if (self.font == NULL || self.text == NULL) 
        return;

    // Initialize the text matrix to a known value
    CGContextRef context = UIGraphicsGetCurrentContext();


    //Reset the transformation
    //Doing this means you have to reset the contentScaleFactor to 1.0
    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);
    if (xScaleFactor != 1.0 || yScaleFactor != 1.0)
        t0 = CGAffineTransformScale(t0, xScaleFactor, yScaleFactor);

    CGContextConcatCTM(context, t0);

    CGContextSetTextMatrix(context, CGAffineTransformIdentity);

    if(ARCVIEW_DEBUG_MODE)
        // Draw a black background (debug)
        CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
        CGContextFillRect(context, self.layer.bounds);
    

    NSAttributedString *attStr = self.attributedString;
    CFAttributedStringRef asr = (CFAttributedStringRef)attStr;
    CTLineRef line = CTLineCreateWithAttributedString(asr);
    assert(line != NULL);

    CFIndex glyphCount = CTLineGetGlyphCount(line);
    if (glyphCount == 0) 
        CFRelease(line);
        return;
    

    GlyphArcInfo *  glyphArcInfo = (GlyphArcInfo*)calloc(glyphCount, sizeof(GlyphArcInfo));
    PrepareGlyphArcInfo(line, glyphCount, glyphArcInfo, _arcSize);

    // Move the origin from the lower left of the view nearer to its center.
    CGContextSaveGState(context);

    CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV - self.radius / 2.0);

    if(ARCVIEW_DEBUG_MODE)
        // Stroke the arc in red for verification.
        CGContextBeginPath(context);
        CGContextAddArc(context, 0.0, 0.0, self.radius, M_PI_2+_arcSize/2.0, M_PI_2-_arcSize/2.0, 1);
        CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
        CGContextStrokePath(context);
    

    // Rotate the context 90 degrees counterclockwise (per 180 degrees)
    CGContextRotateCTM(context, _arcSize/2.0);

    // Now for the actual drawing. The angle offset for each glyph relative to the previous glyph has already been calculated; with that information in hand, draw those glyphs overstruck and centered over one another, making sure to rotate the context after each glyph so the glyphs are spread along a semicircular path.

    CGPoint textPosition = CGPointMake(0.0, self.radius);
    CGContextSetTextPosition(context, textPosition.x, textPosition.y);

    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);
        Boolean drawSubstitutedGlyphsManually = false;
        CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);

        // Determine if we need to draw substituted glyphs manually. Do so if the runFont is not the same as the overall font.
        if (self.dimsSubstitutedGlyphs && ![self.font isEqual:(UIFont *)runFont]) 
            drawSubstitutedGlyphsManually = true;
        

        CFIndex runGlyphIndex = 0;
        for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) 
            CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);
            CGContextRotateCTM(context, -(glyphArcInfo[runGlyphIndex + glyphOffset].angle));

            // 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);

            // Glyphs are positioned relative to the text position for the line, so offset text position leftwards by this glyph's width in preparation for the next glyph.
            textPosition.x -= glyphWidth;

            CGAffineTransform textMatrix = CTRunGetTextMatrix(run);
            textMatrix.tx = positionForThisGlyph.x;
            textMatrix.ty = positionForThisGlyph.y;
            CGContextSetTextMatrix(context, textMatrix);

            if (!drawSubstitutedGlyphsManually) 
                CTRunDraw(run, context, glyphRange);
             
            else 
                // We need to draw the glyphs manually in this case because we are effectively applying a graphics operation by setting the context fill color. Normally we would use kCTForegroundColorAttributeName, but this does not apply as we don't know the ranges for the colors in advance, and we wanted demonstrate how to manually draw.
                CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL);
                CGGlyph glyph;
                CGPoint position;

                CTRunGetGlyphs(run, glyphRange, &glyph);
                CTRunGetPositions(run, glyphRange, &position);

                CGContextSetFont(context, cgFont);
                CGContextSetFontSize(context, CTFontGetSize(runFont));
                CGContextSetRGBFillColor(context, 0.25, 0.25, 0.25, 0.5);
                CGContextShowGlyphsAtPositions(context, &glyph, &position, 1);

                CFRelease(cgFont);
            

            // Draw the glyph bounds 
            if ((self.showsGlyphBounds) != 0) 
                CGRect glyphBounds = CTRunGetImageBounds(run, context, glyphRange);

                CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 1.0);
                CGContextStrokeRect(context, glyphBounds);
            
            // Draw the bounding boxes defined by the line metrics
            if ((self.showsLineMetrics) != 0) 
                CGRect lineMetrics;
                CGFloat ascent, descent;

                CTRunGetTypographicBounds(run, glyphRange, &ascent, &descent, NULL);

                // The glyph is centered around the y-axis
                lineMetrics.origin.x = -halfGlyphWidth;
                lineMetrics.origin.y = positionForThisGlyph.y - descent;
                lineMetrics.size.width = glyphWidth; 
                lineMetrics.size.height = ascent + descent;

                CGContextSetRGBStrokeColor(context, 0.0, 1.0, 0.0, 1.0);
                CGContextStrokeRect(context, lineMetrics);
            
        

        glyphOffset += runGlyphCount;
    

    CGContextRestoreGState(context);

    free(glyphArcInfo);
    CFRelease(line);    





-(void)dealloc

    [_font release];
    [_string release];
    [_color release];
    [super dealloc]


@synthesize font = _font;
@synthesize text = _string;
@synthesize radius = _radius;
@synthesize color = _color;
@synthesize arcSize = _arcSize;
@synthesize shiftH = _shiftH;
@synthesize shiftV = _shiftV;

@dynamic attributedString;
- (NSAttributedString *)attributedString 
    // Create an attributed string with the current font and string.
    assert(self.font != nil);
    assert(self.text != nil);

    // Create our attributes...

    // font
    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)self.font.fontName, self.font.pointSize, NULL);

    // color
    CGColorRef colorRef = self.color.CGColor;

    // pack it into attributes dictionary

    NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
                                    (id)fontRef, (id)kCTFontAttributeName,
                                    colorRef, (id)kCTForegroundColorAttributeName,
                                    nil];
    assert(attributesDict != nil);


    // Create the attributed string
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.text attributes:attributesDict];

    CFRelease(fontRef);

    return [attrString autorelease];


@dynamic showsGlyphBounds;
- (BOOL)showsGlyphBounds 
    return _flags.showsGlyphBounds;


- (void)setShowsGlyphBounds:(BOOL)show 
    _flags.showsGlyphBounds = show ? 1 : 0;


@dynamic showsLineMetrics;
- (BOOL)showsLineMetrics 
    return _flags.showsLineMetrics;


- (void)setShowsLineMetrics:(BOOL)show 
    _flags.showsLineMetrics = show ? 1 : 0;


@dynamic dimsSubstitutedGlyphs;
- (BOOL)dimsSubstitutedGlyphs 
    return _flags.dimsSubstitutedGlyphs;


- (void)setDimsSubstitutedGlyphs:(BOOL)dim 
    _flags.dimsSubstitutedGlyphs = dim ? 1 : 0;


@end

【讨论】:

我在我自己的一个项目中使用了这段代码,我注意到它没有包含dealloc,所以我将它粘贴在下面。 Instruments 还报告了另外 2 个内存泄漏:1)来自 drawRect 和 2)来自对属性字符串的调用。在后者上,它在返回时具有必要的自动释放功能,所以我不确定它为什么会泄漏。如果有人弄清楚其中任何一个,我将不胜感激。 -(void)dealloc [_font release]; [_string 释放]; [_color 发布]; [超级释放]; 谢谢!工作得很好。我需要修改什么来启用倒弧? (意思是“微笑”而不是“哭泣”) @juggleware 您可以随时编辑自己的帖子。我将快速编辑添加您的-dealloc-attributedString 中的泄漏是 fontRef 缺少释放;我也会补充的。我无法弄清楚drawRect: 中的错误是什么,并且clang 静态分析器没有帮助。 如果有人需要与 Avraham Shukron 相同的效果,您必须将 setArcSize 或 setArcRadius 设为负值。这就是让弧线“微笑”而不是“哭”的方式。 @Michael,您是否尝试设置 shiftV 属性?【参考方案9】:

Juggleware 的解决方案效果很好,但我似乎找不到改变方向的方法,即如何将弧线从顺时针移动到逆时针?

更新:在为该示例中过于复杂的代码苦苦挣扎了几天之后,我决定自己动手。我使用 CATextLayers 采用了一种声明性方法,CATextLayers 放置在圆圈上并单独旋转。这样,结果就更容易实现了。这是给你的核心代码:

-(void)layoutSublayersOfLayer:(CALayer*)layer

    if ( layer != self.layer )
    
        return;
    

    self.layer.sublayers = nil;

    LOG( @"Laying out sublayers..." );

    CGFloat xcenter = self.frame.size.width / 2;
    CGFloat ycenter = self.frame.size.height / 2;

    float angle = arcStart;
    float angleStep = arcSize / [self.text length];

    for ( NSUInteger i = 0; i < [self.text length]; ++i )
    
        NSRange range =  .location = i, .length = 1 ;
        NSString* c = [self.text substringWithRange:range];

        CGFloat yoffset = sin( DEGREES_TO_RADIANS(angle) ) * radius;
        CGFloat xoffset = cos( DEGREES_TO_RADIANS(angle) ) * radius;

        CGFloat rotAngle = 90 - angle;

        if ( clockwise )
        
            yoffset = -yoffset;
            rotAngle = -90 + angle;
        

        CATextLayer* tl = [[CATextLayer alloc] init];
        if ( debugMode )
        
            tl.borderWidth = 1;
            tl.cornerRadius = 3;
            tl.borderColor = [UIColor whiteColor].CGColor;
        
        tl.frame = CGRectMake( shiftH + xcenter - xoffset, shiftV + ycenter + yoffset, 20, 20 );
        tl.font = self.font.fontName;
        tl.fontSize = self.font.pointSize;
        tl.foregroundColor = self.color.CGColor;
        tl.string = c;
        tl.alignmentMode = @"center";

        tl.transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeRotation( DEGREES_TO_RADIANS(rotAngle) ) );

        if ( debugMode )
        
            CATextLayer* debugLayer = [self debugLayerWithText:[NSString stringWithFormat:@"%u: %.0f°", i, angle]];
            debugLayer.transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeRotation( DEGREES_TO_RADIANS(-rotAngle) ) );
            [tl addSublayer:debugLayer];
                
        [self.layer addSublayer:tl];

        angle += angleStep;
    

【讨论】:

“arcStart”变量代表什么? arcStart 是您希望弯曲文本开始的偏移量(以度为单位)。通过调整此属性,您可以更改对齐方式。在此代码的后续版本中,我尊重 UITextAlignment 和 UIViewContentMode 属性。如果您有兴趣,我可以将此代码发布到 github。 不需要...已经根据您的代码创建了我自己的版本。谢谢! Tx 获取代码?我对 github 链接非常感兴趣(如果可能的话) 文字似乎模糊不清,想知道为什么?在 Retina 屏幕上显示时变得更加明显【参考方案10】:

取内圆的周长。这是您希望将字符的基础渲染到的圆圈。我们将这个圆周称为totalLength

我假设你有一个字符串列表要围绕textItems 中的圆圈呈现。

将每个字符串的宽度放入textWidths 数组中,然后将它们均匀地分布在totalLength 中,可能像这样的伪(pythonish)代码:

block = max(textWidths)
assert(block * len(textWidths) <= totalLength)
offsets = [(block * i) + ((block-width) / 2) for i, width in enumerate(textWidths)]

虽然毫无疑问可以在断言触发的情况下进行更好的布局,但真正重要的是我们知道单个单词在已知区域中的开始和结束位置。要在长度为 totalLength 的直线上渲染,我们只需在 offsets[i] 处开始渲染每个文本块。

为了将它放到圆上,我们将把这条直线映射回圆周上。为此,我们需要将沿该线的每个像素映射到圆上的一个位置和一个角度。此函数将沿该线的偏移量转换为角度(取值范围为 0 到 totalLength

def offsetToAngle(pixel):
    ratio = pixel / totalLength
    angle = math.pi * 2 * ratio # cool kids use radians.
    return angle

那是你的角度。获得职位:

def angleToPosition(angle, characterWidth):
    xNorm = math.sin(angle + circleRotation)
    yNorm = math.cos(angle + circleRotation)

    halfCWidth = characterWidth / 2
    x = xNorm * radius + yNorm * halfCWidth # +y = tangent
    y = yNorm * radius - xNorm * halfCWidth # -x = tangent again.

    # translate to the circle centre
    x += circleCentre.x
    y += circleCentre.y

    return x,y

这有点棘手。我想,这几乎是您问题的症结所在。最重要的是,您需要沿着圆的切线向后偏移,以计算出开始渲染的点,以便角色的中间到达圆的半径。什么构成“后退”取决于您的坐标系。如果 0,0 在左下角,则交换切线分量的符号。我假设是左上角。

这很重要: 我还做了一个很大的假设,即文本旋转发生在字形的左下角。如果没有,那么事情看起来会有点奇怪。在较大的字体大小下会更加明显。总有一种方法可以补偿它旋转的任何地方,并且通常有一种方法可以告诉系统您希望旋转原点在哪里(这将与您的代码中的 CGContextTranslateCTM 调用有关)你' 需要做一个小实验,让人物在一个点上围绕左下角旋转。

circleRotation 只是一个偏移量,因此您可以旋转整个圆,而不是让事物始终处于相同的方向。这也是弧度。

现在对于每个文本块中的每个字符:

for text, offset in zip(textItems, offsets):
    pix = offset # start each block at the offset we calculated earlier.
    for c in text:
        cWidth = measureGlyph(c)
        # choose the circumference location of the middle of the character
        # this is to match with the tangent calculation of tangentToOffset
        angle = offsetToAngle(pix + cWidth / 2)
        x,y = angleToPosition(angle, cWidth)
        drawGlyph(c, x, y, angle)

        pix += cWidth # start of next character in circumference space

反正就是这个概念。

【讨论】:

当然,如果您可以从底部中间绘制和旋转字形,那么您就不需要切线的东西。【参考方案11】:

我试图在纸上快速计算出来,所以我可能错了:)

将字符串的长度转换为UnitCircle 上的单位。因此(字符串.长度/圆周长)*2Pi。您现在有了整个字符串的弧度角。 (也就是字符串开头和结尾的夹角)

对于单独的字母,您可以执行相同的操作来获取单个字母的角度(以弧度为单位)(使用字母宽度)

一旦你有了弧度的角度,你就可以计算出字母的 x 和 y 位置(和旋转)。

奖励:对于均匀间距,您甚至可以计算出所有字符串的总长度与整个周长之间的比率。并在字符串之间平均分配剩余空间。

更新 我使用 html5/canvas 制作了一个proof of concept,所以用一个像样的浏览器查看它:) 你应该可以移植它。 (请注意,代码未注释)wtf:代码在 chrome 调试控制台打开时运行良好,在关闭时失败。 (解决方法:打开 chrome 控制台:ctrl-shift-j 并重新加载页面:f5); FF3.6.8 似乎还不错,但字母 'dance'。

【讨论】:

我并不是一个数学天才。但我错过了一些我相信的东西。我将字符串分成字符,我知道它们的宽度+高度,但是我怎么知道它们应该放在哪个角度?我采用了您的公式并将它们合并到代码中,但角度实际上并不取决于它看起来的位置?我很讨厌这个;) 我还没有为字符的方向添加任何计算(还)首先要获得正确的位置(用点尝试)然后获得正确的方向。字符的基线与角度正交 感谢 Dribbel 为我提供解决方案。我会尽快更新我的帖子以提供答案【参考方案12】:

查看这个 Apple 示例项目:CoreTextArcCocoa

演示使用 Core Text 进行绘制 Cocoa 中沿弧线的文本 应用。同样,这个样本 说明如何使用 Cocoa 字体面板接收字体设置 Core Text 可以使用 选择用于绘图的字体。

CoreText 也可以在 iOS 中使用,因此您应该能够实现类似的东西。

【讨论】:

感谢您的回复。但不知何故,我无法让它很好地工作。要么我的文字偏离了位置,要么根本没有出现。我让它为 4 个菜单项工作,现在我想添加第五个项目,然后一切都再次变得糟糕。我想我需要更多帮助。 我在帖子中添加了更多代码。解释我现在的立场。

以上是关于现有圆圈上的曲线文本的主要内容,如果未能解决你的问题,请参考以下文章

多行文本文本输入框 textarea 可点击任意地方编辑

ROC曲线——相关文献实例、原理和绘制方法

在 UIImageView 的图像上方绘制贝塞尔曲线

关于C语言中文本文件的逐行读取的实现

Linux 下最大文件数等限制

Unity3D 参数曲线 实现曲线上的匀速运动