I am looking for some up to date help/hints on how to draw simple single line strings around the edge of a circle using Swift2
for iOS9
. I see quite dated examples involving old ObjC fragments, and oft limited to OS X
only. Is this even possible in iOS within a custom UIView
subclass's drawRect()
method?

- 12,762
- 3
- 58
- 74

- 21,522
- 19
- 91
- 167
7 Answers
I was going to say "What have you tried?", but it's Friday afternoon and I got off work early, so I took the opportunity to translate my old ObjC code. Here it is, suitable for Playground. It should be trivial to put it in your UIView.
Swift 2
See below for Swift 3 & Swift 4 updates...
import UIKit
func centreArcPerpendicularText(str: String, context: CGContextRef, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, clockwise: Bool){
// *******************************************************
// This draws the String str around an arc of radius r,
// with the text centred at polar angle theta
// *******************************************************
let l = str.characters.count
let attributes = [NSFontAttributeName: font]
var characters: [String] = [] // This will be an array of single character strings, each character in str
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
characters += [String(str[str.startIndex.advancedBy(i)])]
arcs += [chordToArc(characters[i].sizeWithAttributes(attributes).width, radius: r)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection = clockwise ? -CGFloat(M_PI_2) : CGFloat(M_PI_2)
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = theta - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centerText with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centreText(characters[i], context: context, radius: r, angle: thetaI, colour: c, font: font, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
func centreText(str: String, context: CGContextRef, radius r:CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, slantAngle: CGFloat) {
// *******************************************************
// This draws the String str centred at the position
// specified by the polar coordinates (r, theta)
// i.e. the x= r * cos(theta) y= r * sin(theta)
// and rotated by the angle slantAngle
// *******************************************************
// Set the text attributes
let attributes = [NSForegroundColorAttributeName: c,
NSFontAttributeName: font]
// Save the context
CGContextSaveGState(context)
// Undo the inversion of the Y-axis (or the text goes backwards!)
CGContextScaleCTM(context, 1, -1)
// Move the origin to the centre of the text (negating the y-axis manually)
CGContextTranslateCTM(context, r * cos(theta), -(r * sin(theta)))
// Rotate the coordinate system
CGContextRotateCTM(context, -slantAngle)
// Calculate the width of the text
let offset = str.sizeWithAttributes(attributes)
// Move the origin by half the size of the text
CGContextTranslateCTM (context, -offset.width / 2, -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.drawAtPoint(CGPointZero, withAttributes: attributes)
// Restore the context
CGContextRestoreGState(context)
}
// *******************************************************
// Playground code to test
// *******************************************************
let size = CGSize(width: 256, height: 256)
UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
let context = UIGraphicsGetCurrentContext()!
// *******************************************************************
// Scale & translate the context to have 0,0
// at the centre of the screen maths convention
// Obviously change your origin to suit...
// *******************************************************************
CGContextTranslateCTM (context, size.width / 2, size.height / 2)
CGContextScaleCTM (context, 1, -1)
centreArcPerpendicularText("Hello round world", context: context, radius: 100, angle: 0, colour: UIColor.redColor(), font: UIFont.systemFontOfSize(16), clockwise: true)
centreArcPerpendicularText("Anticlockwise", context: context, radius: 100, angle: CGFloat(-M_PI_2), colour: UIColor.redColor(), font: UIFont.systemFontOfSize(16), clockwise: false)
centreText("Hello flat world", context: context, radius: 0, angle: 0 , colour: UIColor.yellowColor(), font: UIFont.systemFontOfSize(16), slantAngle: CGFloat(M_PI_4))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Update Added clockwise / anticlockwise & straight example.
Update Swift 3
func centreArcPerpendicular(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, clockwise: Bool){
// *******************************************************
// This draws the String str around an arc of radius r,
// with the text centred at polar angle theta
// *******************************************************
let l = str.characters.count
let attributes = [NSFontAttributeName: font]
let characters: [String] = str.characters.map { String($0) } // An array of single character strings, each character in str
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(attributes: attributes).width, radius: r)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection = clockwise ? -CGFloat(M_PI_2) : CGFloat(M_PI_2)
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = theta - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centerText with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: r, angle: thetaI, colour: c, font: font, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, slantAngle: CGFloat) {
// *******************************************************
// This draws the String str centred at the position
// specified by the polar coordinates (r, theta)
// i.e. the x= r * cos(theta) y= r * sin(theta)
// and rotated by the angle slantAngle
// *******************************************************
// Set the text attributes
let attributes = [NSForegroundColorAttributeName: c,
NSFontAttributeName: font]
// Save the context
context.saveGState()
// Undo the inversion of the Y-axis (or the text goes backwards!)
context.scaleBy(x: 1, y: -1)
// Move the origin to the centre of the text (negating the y-axis manually)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
// Rotate the coordinate system
context.rotate(by: -slantAngle)
// Calculate the width of the text
let offset = str.size(attributes: attributes)
// Move the origin by half the size of the text
context.translateBy (x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
context.restoreGState()
}
// *******************************************************
// Playground code to test
// *******************************************************
let size = CGSize(width: 256, height: 256)
UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
let context = UIGraphicsGetCurrentContext()!
// *******************************************************************
// Scale & translate the context to have 0,0
// at the centre of the screen maths convention
// Obviously change your origin to suit...
// *******************************************************************
context.translateBy (x: size.width / 2, y: size.height / 2)
context.scaleBy (x: 1, y: -1)
centreArcPerpendicular(text: "Hello round world", context: context, radius: 100, angle: 0, colour: UIColor.red, font: UIFont.systemFont(ofSize: 16), clockwise: true)
centreArcPerpendicular(text: "Anticlockwise", context: context, radius: 100, angle: CGFloat(-M_PI_2), colour: UIColor.red, font: UIFont.systemFont(ofSize: 16), clockwise: false)
centre(text: "Hello flat world", context: context, radius: 0, angle: 0 , colour: UIColor.yellow, font: UIFont.systemFont(ofSize: 16), slantAngle: CGFloat(M_PI_4))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Swift 4
Yet again, minor changes, this time fixing the deprecation of M_PI
, String
's abandonment of .characters
, the parameter label change in .size(withAttributes...
, and the change in text attributes to the NSAttributedStringKey
enum...
import UIKit
func centreArcPerpendicular(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, clockwise: Bool){
// *******************************************************
// This draws the String str around an arc of radius r,
// with the text centred at polar angle theta
// *******************************************************
let characters: [String] = str.map { String($0) } // An array of single character strings, each character in str
let l = characters.count
let attributes = [NSAttributedStringKey.font: font]
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: r)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection: CGFloat = clockwise ? -.pi / 2 : .pi / 2
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = theta - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centerText with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: r, angle: thetaI, colour: c, font: font, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
func centre(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, slantAngle: CGFloat) {
// *******************************************************
// This draws the String str centred at the position
// specified by the polar coordinates (r, theta)
// i.e. the x= r * cos(theta) y= r * sin(theta)
// and rotated by the angle slantAngle
// *******************************************************
// Set the text attributes
let attributes = [NSAttributedStringKey.foregroundColor: c, NSAttributedStringKey.font: font]
//let attributes = [NSForegroundColorAttributeName: c, NSFontAttributeName: font]
// Save the context
context.saveGState()
// Undo the inversion of the Y-axis (or the text goes backwards!)
context.scaleBy(x: 1, y: -1)
// Move the origin to the centre of the text (negating the y-axis manually)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
// Rotate the coordinate system
context.rotate(by: -slantAngle)
// Calculate the width of the text
let offset = str.size(withAttributes: attributes)
// Move the origin by half the size of the text
context.translateBy (x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
context.restoreGState()
}
// *******************************************************
// Playground code to test
// *******************************************************
let size = CGSize(width: 256, height: 256)
UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
let context = UIGraphicsGetCurrentContext()!
// *******************************************************************
// Scale & translate the context to have 0,0
// at the centre of the screen maths convention
// Obviously change your origin to suit...
// *******************************************************************
context.translateBy (x: size.width / 2, y: size.height / 2)
context.scaleBy(x: 1, y: -1)
centreArcPerpendicular(text: "Hello round world", context: context, radius: 100, angle: 0, colour: UIColor.red, font: UIFont.systemFont(ofSize: 16), clockwise: true)
centreArcPerpendicular(text: "Anticlockwise", context: context, radius: 100, angle: CGFloat(-M_PI_2), colour: UIColor.red, font: UIFont.systemFont(ofSize: 16), clockwise: false)
centre(text: "Hello flat world", context: context, radius: 0, angle: 0 , colour: UIColor.yellow, font: UIFont.systemFont(ofSize: 16), slantAngle: .pi / 4)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Update to show use in UIView
Commentator @RitvikUpadhyaya asks how to do this in a UIView
- obvious to old hands, but not perhaps to beginners. The trick is to get the right context using UIGraphicsGetCurrentContext
without calling UIGraphicsBeginImageContextWithOptions
(which overrides the UIView
's context as the current context) - therefore your UIView
should look like this:
class MyView: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let size = self.bounds.size
context.translateBy (x: size.width / 2, y: size.height / 2)
context.scaleBy (x: 1, y: -1)
centreArcPerpendicular(text: "Hello round world", context: context, radius: 100, angle: 0, colour: UIColor.red, font: UIFont.systemFont(ofSize: 16), clockwise: true)
centreArcPerpendicular(text: "Anticlockwise", context: context, radius: 100, angle: CGFloat(-M_PI_2), colour: UIColor.red, font: UIFont.systemFont(ofSize: 16), clockwise: false)
centre(text: "Hello flat world", context: context, radius: 0, angle: 0 , colour: UIColor.yellow, font: UIFont.systemFont(ofSize: 16), slantAngle: CGFloat(M_PI_4))
}
}

- 22,115
- 10
- 72
- 85
-
26Upvoted for including the most amazingly extensive commenting I've seen in 40 years... – matt Sep 25 '15 at 15:31
-
11I'm telling you, when you get to my age, there is /no way/ I could remember what all my code does without it!!! :) – Grimxn Sep 25 '15 at 15:33
-
2This is an absolutely stunning answer! Would you please add the license under which your snippet is available? Corporate friendly MIT, BSD or Apache would be great :) – Rafael Nobre Aug 02 '16 at 05:48
-
1Thank you! My understanding is that the SE terms (http://stackexchange.com/legal) automatically apply Creative Commons Attribution Share Alike license to all material submitted... – Grimxn Aug 02 '16 at 07:33
-
1In case you don't need the text to be centered, but start from the initial coordinate, change the line var thetaI = theta - direction * totalArc / 2 to var thetaI = theta; – JoeGalind Sep 04 '16 at 04:05
-
In Swift 3, UIColor.red() should be UIColor.red – charles.cc.hsu Sep 21 '16 at 04:59
-
This answer looks like it might help me, but how do I apply it to a UILabel. Do I add these functions into a UILabel subclass? How do I get the context of a UILabel? – richc Sep 22 '16 at 13:21
-
1`UILabel` inherits from `UIView`, so subclass `UILabel` then put this in your `override func draw(_ rect: CGRect)`. – Grimxn Sep 22 '16 at 16:30
-
hey, thx for yr help. I have added this to drawRect... but how do I link to the label.text?? let context = UIGraphicsGetCurrentContext() let size = CGSize(width: self.frame.width, height: self.frame.height) CGContextTranslateCTM (context, size.width / 2, size.height / 2) CGContextScaleCTM (context, 1, -1) centreArcPerpendicularText("Hello round world", context: context!, radius: 100, angle: 0, colour: UIColor.redColor(), font: UIFont.systemFontOfSize(16), clockwise: true) – richc Sep 22 '16 at 20:35
-
If you have subclassed UILabel then the text will be self.text. – Grimxn Sep 22 '16 at 21:40
-
ahhh! The text had been translated so out of view. Now sorted. Superb answers! Thx. – richc Sep 23 '16 at 09:33
-
Why don't you add your implementation as a second answer? It's a non-trivial addition! – Grimxn Sep 24 '16 at 08:33
-
1@Grimxn could you tell me how to put this in a view. I tried putting the code in the `draw(_rect: CGRect)` func but the text does not draw! The translation is `context.translateBy (x: size.width / 2, y: size.height / 2)` so that should draw it in the centre! Sorry I dont have much experience with coreanimation so it is not as trivial to me yet! – Ritvik Upadhyaya Jan 09 '17 at 11:23
-
1@RitvikUpadhyaya - the trick is *not* to call `UIGraphicsBeginImageContextWithOptions` but to use the `UIView`'s own context which is current at that point - have updated the answer with example... – Grimxn Jan 09 '17 at 12:31
-
2An epic answer - thank you so much for the work you put in to this! – Mark Brittingham Jan 18 '17 at 18:02
-
@MarkBrittingham - you're very welcome. I wrote it for an astrolabe app years ago, glad you liked it, and very happy to share! – Grimxn Jan 18 '17 at 22:36
-
1@Grimxn, I accepted your challenge to add the UILabel implementation! – Mark Moeykens Mar 12 '17 at 09:03
-
It there any xamarin implementation? – Nininea Oct 17 '17 at 20:59
-
@Nininea - I don't use Xamarin, but the logic of drawing each character shouldn't change, only the `centre:...` function which actually renders each glyph. – Grimxn Oct 18 '17 at 08:30
-
I have tried same code in my project usinf xcode 9.3. The text color not changing. The default text showing black. Any suggestion? – Siva Apr 26 '18 at 07:40
-
@Siva - I have updated it to Swift 4. The handling of Attributes (font colour, etc.) changed in Swift 4... – Grimxn Apr 26 '18 at 10:19
-
@Grimxn Thanks for your reply. I have written like same but label text color not changing here – Siva Apr 26 '18 at 10:48
-
How can I draw a new line character??? I need to draw text in multiline – Raj Aggrawal Apr 08 '19 at 05:57
-
@RajAggrawal - the code already shows you how to get the size of text (it uses it for the width, but it also gets the height). From that it should be trivial to call with different radii for each line. – Grimxn Apr 08 '19 at 09:55
-
Outstanding bit of work! (voted). You forgot to update your UIView code for Swift 4 however. It still references M_PI constants. – Duncan C Oct 01 '19 at 12:19
-
I thought my trig was pretty good, but it took me 15 minutes to figure out what the `chordToArc(_: radius:)` function does, exactly. I forgot that the chord line for a character would be tangent to the circle, and therefore form a right triangle, so I couldn't make sense of the math. – Duncan C Oct 01 '19 at 12:22
-
1Great for Apple Watch OS7 new Extra Large Circular Graphic Complication! Thanks! – Kurt L. Jun 30 '20 at 20:54
-
@Nininea - did you see the additional answer by @wolfgang-schreurs? – Grimxn Dec 28 '20 at 20:58
-
@Grimxn I tried your example. but it is not centering the middle text. Here is video that i have tried. https://drive.google.com/file/d/1MjAaDDrnuXdE61J0cdSF5i4MuwWx61Ob/view?usp=sharing and here is the video which I am trying to acheive https://drive.google.com/file/d/1Qzj1vInxDRz9uFI4mzNw5CR0h49rAC6w/view?usp=sharing – Shahryar Rafique Oct 09 '21 at 08:28
@IBDesignable For UILabel on Circular Path
First of all, I think we can all agree that @Grimxn is THE MAN! His solution kicks butt. I took his work and refactored it into a custom UILabel control that you can set and edit on the Storyboard. If you guys watch my videos you know how much I love to do this stuff!
Swift 3 Code for Custom UILabel
import UIKit
@IBDesignable
class UILabelX: UILabel {
// *******************************************************
// DEFINITIONS (Because I'm not brilliant and I'll forget most this tomorrow.)
// Radius: A straight line from the center to the circumference of a circle.
// Circumference: The distance around the edge (outer line) the circle.
// Arc: A part of the circumference of a circle. Like a length or section of the circumference.
// Theta: A label or name that represents an angle.
// Subtend: A letter has a width. If you put the letter on the circumference, the letter's width
// gives you an arc. So now that you have an arc (a length on the circumference) you can
// use that to get an angle. You get an angle when you draw a line from the center of the
// circle to each end point of your arc. So "subtend" means to get an angle from an arc.
// Chord: A line segment connecting two points on a curve. If you have an arc then there is a
// start point and an end point. If you draw a straight line from start point to end point
// then you have a "chord".
// sin: (Super simple/incomplete definition) Or "sine" takes an angle in degrees and gives you a number.
// asin: Or "asine" takes a number and gives you an angle in degrees. Opposite of sine.
// More complete definition: http://www.mathsisfun.com/sine-cosine-tangent.html
// cosine: Also takes an angle in degrees and gives you another number from using the two radiuses (radii).
// *******************************************************
@IBInspectable var angle: CGFloat = 1.6
@IBInspectable var clockwise: Bool = true
override func draw(_ rect: CGRect) {
centreArcPerpendicular()
}
/**
This draws the self.text around an arc of radius r,
with the text centred at polar angle theta
*/
func centreArcPerpendicular() {
guard let context = UIGraphicsGetCurrentContext() else { return }
let str = self.text ?? ""
let size = self.bounds.size
context.translateBy(x: size.width / 2, y: size.height / 2)
let radius = getRadiusForLabel()
let l = str.characters.count
let attributes: [String : Any] = [NSFontAttributeName: self.font]
let characters: [String] = str.characters.map { String($0) } // An array of single character strings, each character in str
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(attributes: attributes).width, radius: radius)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection = clockwise ? -CGFloat(M_PI_2) : CGFloat(M_PI_2)
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = angle - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centre with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
/**
This draws the String str centred at the position
specified by the polar coordinates (r, theta)
i.e. the x= r * cos(theta) y= r * sin(theta)
and rotated by the angle slantAngle
*/
func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
// Set the text attributes
let attributes = [NSForegroundColorAttributeName: self.textColor,
NSFontAttributeName: self.font] as [String : Any]
// Save the context
context.saveGState()
// Move the origin to the centre of the text (negating the y-axis manually)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
// Rotate the coordinate system
context.rotate(by: -slantAngle)
// Calculate the width of the text
let offset = str.size(attributes: attributes)
// Move the origin by half the size of the text
context.translateBy(x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
context.restoreGState()
}
func getRadiusForLabel() -> CGFloat {
// Imagine the bounds of this label will have a circle inside it.
// The circle will be as big as the smallest width or height of this label.
// But we need to fit the size of the font on the circle so make the circle a little
// smaller so the text does not get drawn outside the bounds of the circle.
let smallestWidthOrHeight = min(self.bounds.size.height, self.bounds.size.width)
let heightOfFont = self.text?.size(attributes: [NSFontAttributeName: self.font]).height ?? 0
// Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
return (smallestWidthOrHeight/2) - heightOfFont + 5
}
}
Example of Usage on Storyboard
Changes I made
- I removed parameters that I could get straight from the label now.
- I'm admittedly not the smartest in Trigonometry and have forgotten a lot at my age so I included all the relevant definitions so I could start to understand @Grimxn brilliancy.
- The angle and clockwise settings are now properties you can adjust in Attributes Inspector.
- I create the radius from the size of the label now.
- Put some of the comments in standard format on functions, you know, so you get that popup that comes up with you OPTION + CLICK functions.
Problems I have seen
I encourage you to edit the above to improve it.
- I don't know why but sometimes the label kept rendering over other controls even though it was behind them in the document outline.

- 1
- 1

- 15,915
- 6
- 63
- 62
-
2Well done! The warning is fixed by changing `let attributes = [NSFontAttributeName: self.font]` to `let attributes: [String : Any] = [NSFontAttributeName: self.font]`. The change in the type of attributes dictionaries came about in Swift 3, IIRC... – Grimxn Mar 12 '17 at 09:50
-
-
Great but how can you set a radius that is not depend on the width/height? I want to just be able to set an arc, and change it dynamically, and even to bring it back to be flat ? https://stackoverflow.com/questions/44967174/curved-text-code-modification – Curnelious Jul 07 '17 at 09:24
-
@MarkMoeykens It doesnt work at all, even I set angle to 1 or 2 it put the text in random place. Why? – Bartłomiej Semańczyk Aug 21 '20 at 06:43
-
@MarkMoeykens what should I do to rotate label -60 degrees left? Let the text starts at 10 o clock, direction clockwise. – Bartłomiej Semańczyk Aug 21 '20 at 07:01
Always the same implementation but adjusted for Swift 4
import UIKit
@IBDesignable
class CircularLabel: UILabel {
// *******************************************************
// DEFINITIONS (Because I'm not brilliant and I'll forget most this tomorrow.)
// Radius: A straight line from the center to the circumference of a circle.
// Circumference: The distance around the edge (outer line) the circle.
// Arc: A part of the circumference of a circle. Like a length or section of the circumference.
// Theta: A label or name that represents an angle.
// Subtend: A letter has a width. If you put the letter on the circumference, the letter's width
// gives you an arc. So now that you have an arc (a length on the circumference) you can
// use that to get an angle. You get an angle when you draw a line from the center of the
// circle to each end point of your arc. So "subtend" means to get an angle from an arc.
// Chord: A line segment connecting two points on a curve. If you have an arc then there is a
// start point and an end point. If you draw a straight line from start point to end point
// then you have a "chord".
// sin: (Super simple/incomplete definition) Or "sine" takes an angle in degrees and gives you a number.
// asin: Or "asine" takes a number and gives you an angle in degrees. Opposite of sine.
// More complete definition: http://www.mathsisfun.com/sine-cosine-tangent.html
// cosine: Also takes an angle in degrees and gives you another number from using the two radiuses (radii).
// *******************************************************
@IBInspectable var angle: CGFloat = 1.6
@IBInspectable var clockwise: Bool = true
override func draw(_ rect: CGRect) {
centreArcPerpendicular()
}
/**
This draws the self.text around an arc of radius r,
with the text centred at polar angle theta
*/
func centreArcPerpendicular() {
guard let context = UIGraphicsGetCurrentContext() else { return }
let string = text ?? ""
let size = bounds.size
context.translateBy(x: size.width / 2, y: size.height / 2)
let radius = getRadiusForLabel()
let l = string.count
let attributes = [NSAttributedStringKey.font : self.font!]
let characters: [String] = string.map { String($0) } // An array of single character strings, each character in str
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: radius)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection = clockwise ? -CGFloat.pi/2 : CGFloat.pi/2
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = angle - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centre with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
/**
This draws the String str centred at the position
specified by the polar coordinates (r, theta)
i.e. the x= r * cos(theta) y= r * sin(theta)
and rotated by the angle slantAngle
*/
func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
// Set the text attributes
let attributes : [NSAttributedStringKey : Any] = [
NSAttributedStringKey.foregroundColor: textColor!,
NSAttributedStringKey.font: font!
]
// Save the context
context.saveGState()
// Move the origin to the centre of the text (negating the y-axis manually)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
// Rotate the coordinate system
context.rotate(by: -slantAngle)
// Calculate the width of the text
let offset = str.size(withAttributes: attributes)
// Move the origin by half the size of the text
context.translateBy(x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
context.restoreGState()
}
func getRadiusForLabel() -> CGFloat {
// Imagine the bounds of this label will have a circle inside it.
// The circle will be as big as the smallest width or height of this label.
// But we need to fit the size of the font on the circle so make the circle a little
// smaller so the text does not get drawn outside the bounds of the circle.
let smallestWidthOrHeight = min(bounds.size.height, bounds.size.width)
let heightOfFont = text?.size(withAttributes: [NSAttributedStringKey.font: self.font]).height ?? 0
// Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
return (smallestWidthOrHeight/2) - heightOfFont + 5
}
}

- 8,379
- 6
- 63
- 81
-
1One tiny addition I made was to add a "isRTL" boolean for right-to-left languages which just negates the direction but not the slantCorrection (so the text is not "upside down") – GilroyKilroy Jan 11 '18 at 21:19
- Swift 5
- Transparent background
- Kern option for text
- Correct ratio for view
Full playground code
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
// *******************************************************
// Playground code to test
// *******************************************************
let size = CGSize(width: 256, height: 256)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!
// *******************************************************************
// Scale & translate the context to have 0,0
// at the centre of the screen maths convention
// Obviously change your origin to suit...
// *******************************************************************
context.translateBy (x: size.width / 2, y: size.height / 2)
context.scaleBy(x: 1, y: -1)
centreArcPerpendicular(text: "Hello round World",
context: context,
radius: size.height * 0.44,
angle: 0,
colour: UIColor.white,
font: UIFont.systemFont(ofSize: 16),
clockwise: true,
kern: 3)
centreArcPerpendicular(text: "Anticlockwise",
context: context,
radius: size.height * 0.44,
angle: -.pi,
colour: UIColor.white,
font: UIFont.systemFont(ofSize: 16),
clockwise: false,
kern: 3)
centre(text: "Hello center world", context: context, radius: 0, angle: 0 , colour: UIColor.yellow, font: UIFont.systemFont(ofSize: 16), slantAngle: .pi/4, kern: 0)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
func centreArcPerpendicular(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, clockwise: Bool, kern: CGFloat = 0) {
// *******************************************************
// This draws the String str around an arc of radius r,
// with the text centred at polar angle theta
// *******************************************************
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
return 2 * asin(chord / (2 * radius))
}
let characters: [String] = str.map { String($0) } // An array of single character strings, each character in str
let l = characters.count
let attributes = [NSAttributedString.Key.font: font,
NSAttributedString.Key.kern: kern] as [NSAttributedString.Key : Any]
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: r)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection: CGFloat = clockwise ? -.pi / 2 : .pi / 2
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = theta - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centerText with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: r, angle: thetaI, colour: c, font: font, slantAngle: thetaI + slantCorrection, kern: kern)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func centre(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, slantAngle: CGFloat, kern: CGFloat) {
// *******************************************************
// This draws the String str centred at the position
// specified by the polar coordinates (r, theta)
// i.e. the x= r * cos(theta) y= r * sin(theta)
// and rotated by the angle slantAngle
// *******************************************************
// Set the text attributes
let attributes = [NSAttributedString.Key.foregroundColor: c,
NSAttributedString.Key.font: font,
NSAttributedString.Key.kern: kern] as [NSAttributedString.Key : Any]
//let attributes = [NSForegroundColorAttributeName: c, NSFontAttributeName: font]
// Save the context
context.saveGState()
// Undo the inversion of the Y-axis (or the text goes backwards!)
context.scaleBy(x: 1, y: -1)
// Move the origin to the centre of the text (negating the y-axis manually)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
// Rotate the coordinate system
context.rotate(by: -slantAngle)
// Calculate the width of the text
let offset = str.size(withAttributes: attributes)
// Move the origin by half the size of the text
context.translateBy (x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
context.restoreGState()
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
@IBDesignable For UILabel on Circular Path for Swift 2
Big thanks to both @Grimxn and @mark-moeykens for the absolutely killer work. I've done a small refactor on Mark's work so I could use it in a project that hasn't taken the time to update to Swift 3. Wanted to share, since the previous posts were so helpful.
Swift 2 Code for Custom UILabel
import UIKit
@IBDesignable
class ArcUILabel: UILabel
{
// *******************************************************
// DEFINITIONS (Because I'm not brilliant and I'll forget most this tomorrow.)
// Radius: A straight line from the center to the circumference of a circle.
// Circumference: The distance around the edge (outer line) the circle.
// Arc: A part of the circumference of a circle. Like a length or section of the circumference.
// Theta: A label or name that represents an angle.
// Subtend: A letter has a width. If you put the letter on the circumference, the letter's width
// gives you an arc. So now that you have an arc (a length on the circumference) you can
// use that to get an angle. You get an angle when you draw a line from the center of the
// circle to each end point of your arc. So "subtend" means to get an angle from an arc.
// Chord: A line segment connecting two points on a curve. If you have an arc then there is a
// start point and an end point. If you draw a straight line from start point to end point
// then you have a "chord".
// sin: (Super simple/incomplete definition) Or "sine" takes an angle in degrees and gives you a number.
// asin: Or "asine" takes a number and gives you an angle in degrees. Opposite of sine.
// More complete definition: http://www.mathsisfun.com/sine-cosine-tangent.html
// cosine: Also takes an angle in degrees and gives you another number from using the two radiuses (radii).
// *******************************************************
@IBInspectable var angle: CGFloat = 1.6
@IBInspectable var clockwise: Bool = true
override func drawRect(rect: CGRect)
{
centreArcPerpendicular()
}
/**
This draws the self.text around an arc of radius r,
with the text centred at polar angle theta
*/
func centreArcPerpendicular() {
guard let context = UIGraphicsGetCurrentContext() else { return }
let str = self.text ?? ""
let size = self.bounds.size
CGContextTranslateCTM(context, size.width / 2, size.height / 2)
let radius = getRadiusForLabel()
let l = str.characters.count
let attributes: [String : AnyObject] = [NSFontAttributeName: self.font]
let characters: [String] = str.characters.map { String($0) } // An array of single character strings, each character in str
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
arcs += [chordToArc(characters[i].sizeWithAttributes(attributes).width, radius: radius)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection = clockwise ? -CGFloat(M_PI_2) : CGFloat(M_PI_2)
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = angle - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centre with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
/**
This draws the String str centred at the position
specified by the polar coordinates (r, theta)
i.e. the x= r * cos(theta) y= r * sin(theta)
and rotated by the angle slantAngle
*/
func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
// Set the text attributes
let attributes = [NSForegroundColorAttributeName: self.textColor,
NSFontAttributeName: self.font] as [String : AnyObject]
// Save the context
CGContextSaveGState(context)
// Move the origin to the centre of the text (negating the y-axis manually)
CGContextTranslateCTM(context, r * cos(theta), -(r * sin(theta)))
// Rotate the coordinate system
CGContextRotateCTM(context, -slantAngle)
// Calculate the width of the text
let offset: CGSize = str.sizeWithAttributes(attributes)
// Move the origin by half the size of the text
CGContextTranslateCTM(context, -offset.width / 2, -offset.height / 2)
// Draw the text
let txtStr = NSString(string: str)
txtStr.drawAtPoint(CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
CGContextRestoreGState(context)
}
func getRadiusForLabel() -> CGFloat {
// Imagine the bounds of this label will have a circle inside it.
// The circle will be as big as the smallest width or height of this label.
// But we need to fit the size of the font on the circle so make the circle a little
// smaller so the text does not get drawn outside the bounds of the circle.
let smallestWidthOrHeight = min(self.bounds.size.height, self.bounds.size.width)
let heightOfFont = self.text?.sizeWithAttributes([NSFontAttributeName: self.font]).height ?? 0
// Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
return (smallestWidthOrHeight/2) - heightOfFont + 5
}
}

- 1
- 1

- 1,851
- 1
- 13
- 13
A C#-version based on the code from Grimxm:
private void CenterArcPerpendicular(string text, CGContext context, float radius, double angle, UIColor textColor, UIFont font, bool isClockwise) {
var characters = text.ToCharArray();
var arcs = new List<float>() { };
float totalArc = 0;
for (var i = 0; i < characters.Length; i++)
{
var character = new NSString(new string(new char[] { characters[i] }));
var charSize = character.StringSize(font);
var arc = ChordToArc((float)charSize.Width, radius);
arcs.Add(arc);
totalArc += arc;
}
var direction = isClockwise ? -1 : 1;
var slantCorrection = (float)(isClockwise ? -(Math.PI / 2) : (Math.PI / 2));
var thetaI = angle - (direction * (totalArc / 2));
for (var i = 0; i < characters.Length; i++)
{
var character = new NSString(new string(new char[] { characters[i] }));
thetaI += direction * arcs[i] / 2;
CenterText(character, context: context, radius: radius, angle: thetaI, textColor: textColor, font: font, slantAngle: thetaI + slantCorrection);
thetaI += direction * arcs[i] / 2;
}
}
private float ChordToArc(float chord, float radius) {
return (float)(2 * Math.Asin(chord / (2 * radius)));
}
private void CenterText(NSString text, CGContext context, float radius, double angle, UIColor textColor, UIFont font, double slantAngle)
{
var attributes = new UIStringAttributes { Font = font, ForegroundColor = textColor };
context.SaveState();
context.ScaleCTM(1, -1);
var dX = radius * Math.Cos(angle);
var dY = -(radius * Math.Sin(angle));
context.TranslateCTM((nfloat)dX, (nfloat)dY);
context.RotateCTM(-(nfloat)slantAngle);
var offset = text.StringSize(font);
context.TranslateCTM(-offset.Width / 2, -offset.Height / 2);
text.DrawString(CGPoint.Empty, attributes);
context.RestoreState();
}
For use in a Xamarin iOS app.

- 11,779
- 7
- 51
- 92
Update code to Swift 5
import Foundation
import UIKit
@IBDesignable
class UILabelX: UILabel {
@IBInspectable var angle: CGFloat = 1.6
@IBInspectable var clockwise: Bool = true
override func draw(_ rect: CGRect) {
centreArcPerpendicular()
}
/**
This draws the self.text around an arc of radius r,
with the text centred at polar angle theta
*/
func centreArcPerpendicular() {
guard let context = UIGraphicsGetCurrentContext() else { return }
let str = self.text ?? ""
let size = self.bounds.size
context.translateBy(x: size.width / 2, y: size.height / 2)
let radius = getRadiusForLabel()
let l = str.count
// let attributes: [String : Any] = [NSAttributedString.Key: self.font]
let attributes : [NSAttributedString.Key : Any] = [.font : self.font]
let characters: [String] = str.map { String($0) } // An array of single character strings, each character in str
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
// arcs = [chordToArc(characters[i].widthOfString(usingFont: self.font), radius: radius)]
arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: radius)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection = clockwise ? -CGFloat(Double.pi/2) : CGFloat(Double.pi/2)
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = angle - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centre with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
/**
This draws the String str centred at the position
specified by the polar coordinates (r, theta)
i.e. the x= r * cos(theta) y= r * sin(theta)
and rotated by the angle slantAngle
*/
func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
// Set the text attributes
let attributes = [NSAttributedString.Key.font: self.font!] as [NSAttributedString.Key : Any]
// Save the context
context.saveGState()
// Move the origin to the centre of the text (negating the y-axis manually)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
// Rotate the coordinate system
context.rotate(by: -slantAngle)
// Calculate the width of the text
let offset = str.size(withAttributes: attributes)
// Move the origin by half the size of the text
context.translateBy(x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
context.restoreGState()
}
func getRadiusForLabel() -> CGFloat {
// Imagine the bounds of this label will have a circle inside it.
// The circle will be as big as the smallest width or height of this label.
// But we need to fit the size of the font on the circle so make the circle a little
// smaller so the text does not get drawn outside the bounds of the circle.
let smallestWidthOrHeight = min(self.bounds.size.height, self.bounds.size.width)
let heightOfFont = self.text?.size(withAttributes: [NSAttributedString.Key.font: self.font]).height ?? 0
// Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
return (smallestWidthOrHeight/2) - heightOfFont + 5
}
}

- 191
- 1
- 5