Why isn't my curved text centering itself?


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:

<hr />// 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]; <hr />


#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


#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?


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.


  • WKInterfaceSwitch or WKInterfaceButton in a table row — which row was touched?
  • want to call ViewController's method from appdelegate
  • Getting SIGABRT in custom uiview using xib in Xcode 6 using Swift
  • Loading view programatically
  • Should all reference to programmactically created subviews declared as weak?
  • viewWillAppear is not called in my view controller when the app becomes active
  • How to prevent specific characters from being typed in textfield?
  • How to send multiple buttons in button.addTarget action? Swift3
  • UIScrollView not scrolling programmatically in iOS 6 & 7 both
  • How to change Picker font colour and size in Xamarin forms?
  • how to specify height of row?
  • Change to wantsSoftwareDimming in iOS 6?
  • Swift class that inherits an Objective-C Base Class not casting to correct type
  • 'Terminating app due to uncaught exception'.. it couldn't find MainWindow, which no l
  • Merge two imageViews into one and save iOS swift
  • Google analytics not sending any hits in iOS?
  • Cocos2D set sprite position in relation to another sprite
  • How to Update UILabel from Another ViewController
  • Adjust UILabel Dynamically Within UITableViewCell?
  • How to style GCKUIMiniMediaControlsViewController in iOS?
  • Forms with Prism: Application windows are expected to have a root VC
  • CKeditor stripping font tags instead of converting to span
  • Ionic Slide Up Slide Down Animation for only one view in sidebar
  • Why can't UI components be accessed from a backgroundworker?
  • LESS CSS how to modify parent property in mixin
  • Prevent Tomcat from caching request during starup
  • Firefox Extension - Monitor refresh and change of tab
  • C# - Serializing and deserializing static member
  • How do I fake an specific browser client when using Java's Net library?
  • How to get a value (ex: baseURL) in every Karate feature?
  • Why winpcap requires both .lib and .dll to run?
  • Return words with double consecutive letters
  • Python: how to group similar lists together in a list of lists?
  • Checking variable from a different class in C#
  • Busy indicator not showing up in wpf window [duplicate]
  • costura.fody for a dll that references another dll
  • Observable and ngFor in Angular 2
  • UserPrincipal.Current returns apppool on IIS
  • java string with new operator and a literal
  • How to push additional view controllers onto NavigationController but keep the TabBar?