Why isn't my curved text centering itself?

2019-09-08 18:28发布

问题:

I have a UIView subclass called dpCurvedLabel. It uses CATextLayers to curved text around an arc. It works fine, except that I can't get it perfectly centered in the layers parent view. I want the center point for the arc to be at the very center of the view (even if the view is smaller) so all the text characters are the same distance from the center.

I can get it CLOSE but it's always a at least few pixels off. The amount it's 'off' seems to be effected by the frame size I give each CATextLayer. There's something wrong with the math, but I can't figure out what. My code:


// instantiate a dpCurvedLabel in a super view
dpCurvedLabel *curvedLabel = [[dpCurvedLabel alloc] 
initWithFrame:CGRectMake(0, 0, 200, 200) arcSize:360 radius:50 
text:@"curve curve curve " font:[UIFont fontWithName:@"HelveticaNeue" size:18] 
textColor:[UIColor whiteColor]];

// You can animate a rotation to see a more pronounced effect
// [curvedLabel rotateContinously];

[self addSubview:curvedLabel];

dpCurvedLabel.h

#import <UIKit/UIKit.h>

@interface dpCurvedLabel : UIView

@property CGFloat arcSize;
@property CGFloat radius;
@property (strong) NSString *text;
@property (strong) UIFont *font;
@property (strong) UIColor *textColor;

- (id)initWithFrame:(CGRect)frame arcSize:(CGFloat)arcSize radius:(CGFloat)radius
text:(NSString *)text font:(UIFont *)font 
textColor:(UIColor *)textColor;
- (void)rotateContinously;

+ (void)makeCurvedText:(CALayer *)layer arcSize:(CGFloat)arcSize radius:(CGFloat)radius 
text:(NSString *)text font:(UIFont *)font textColor:(UIColor *)textColor;

@end

dpCurvedLabel.m

#import "dpCurvedLabel.h"

@implementation dpCurvedLabel

- (id)initWithFrame:(CGRect)frame arcSize:(CGFloat)arcSize radius:(CGFloat)radius text:(NSString *)text font:(UIFont *)font textColor:(UIColor *)textColor
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code

        self.opaque = NO;
        self.clipsToBounds = NO;

        self.arcSize = arcSize;
        self.radius = radius;
        self.text = text;
        self.font = font;
        self.textColor = textColor;
    }
    return self;
}



- (void)layoutSublayersOfLayer:(CALayer *)layer
{
    [super layoutSublayersOfLayer:layer];

    NSLog(@"laying out sublayers!");
    [dpCurvedLabel makeCurvedText:layer arcSize:self.arcSize radius:self.radius text:self.text font:self.font textColor:self.textColor];
}

+ (void)makeCurvedText:(CALayer *)layer arcSize:(CGFloat)arcSize radius:(CGFloat)radius text:(NSString *)text font:(UIFont *)font textColor:(UIColor *)textColor
{

    layer.sublayers = nil;
    layer.masksToBounds = NO;

    CGFloat arcStart = 0;
    CGFloat shiftH = 0;
    CGFloat shiftV = 0;


    BOOL clockwise = YES;
    BOOL debugMode = YES;



    CGFloat xcenter = CGRectGetMidX(layer.bounds);
    CGFloat ycenter = CGRectGetMidY(layer.bounds);

    CGFloat angle = arcStart;
    CGFloat angleStep = arcSize / text.length;

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

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

        CGFloat rotAngle = 90 - angle;

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

        CATextLayer* tl = [[CATextLayer alloc] init];
        tl.masksToBounds = NO;
        tl.wrapped = NO;
        tl.truncationMode = kCATruncationNone;

        if ( debugMode )
        {
            tl.borderWidth = 1;
            tl.cornerRadius = 3;
            tl.borderColor = [UIColor whiteColor].CGColor;
        }

        // Text layer frame determined here. Effects how arc is centered. 
        CGSize charSize = CGSizeMake(20, 20);
        tl.frame = CGRectMake( shiftH + xcenter - xoffset, shiftV + ycenter + yoffset, charSize.width, charSize.height );
        // *******

        tl.font = (__bridge CFTypeRef)(font.fontName);
        tl.fontSize = font.pointSize;
        tl.foregroundColor = textColor.CGColor;
        tl.string = c;
        tl.alignmentMode = @"center";

        tl.transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeRotation( degreesToRadians(rotAngle) ) );
        [layer addSublayer:tl];
        angle += angleStep;
    }

    if ( debugMode )
    {
        layer.backgroundColor = RGBA(0x00, 0x00, 0x00, .6).CGColor;
    }
}

- (void)rotateContinously
{
    CABasicAnimation *rotationAnimation;
    rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * 1 * 1 ];
    rotationAnimation.duration = 5;
    rotationAnimation.cumulative = YES;
    rotationAnimation.repeatCount = INT_MAX;

    [self.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}
@end

What's wrong with the math here? Why won't this text arc center itself?

回答1:

The problem comes from the fact that you are setting the frame of each text layer instead of its position. This means that you are positioning the lower left corner to be at the location where the position should be. Do this instead:

    tl.position = CGPointMake( shiftH + xcenter - xoffset, shiftV + ycenter + yoffset);
    tl.bounds   = CGRectMake(0,0,charSize.width,charSize.height);

And you will find your layers to be exactly where you want them.