在Swift for iOS中沿着圆形path绘制文本

我正在寻找一些最新的帮助/提示关于如何使用Swift2 for iOS9在圆的边缘绘制简单的单行string。 我看到相当陈旧的例子涉及旧的ObjC片段,而且仅限于OS X 这是甚至可能在自定义的UIView子类的drawRect()方法中的iOS?

我会说“你试了些什么?”,但星期五下午,我早点下class,所以我借此机会翻译了我的旧的ObjC代码。 在这里,适合游乐场。 把它放在你的UIView应该是微不足道的。

Swift 2
请参阅下面的Swift 3更新…

 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) // ie 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() 

输出是: 产量

更新添加顺时针/逆时针和直的例子。

更新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) // ie 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() 

更新显示在UIView中使用

评论员@RitvikUpadhyaya问如何在UIView做到这一点 – 显然老手,但不是初学者。 诀窍是使用UIGraphicsGetCurrentContext获取正确的上下文而不调用UIGraphicsBeginImageContextWithOptions (它将覆盖UIView的上下文作为当前的上下文) – 因此你的UIView应该看起来像这样:

 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)) } } 

对于循环path上的UILabel @IBDesignable

首先,我想我们都可以同意@Grimxn是人! 他的解决scheme踢屁股。 我把他的工作,重构成一个自定义的UILabel控件,你可以在故事板上设置和编辑。 如果你们看我的video,你知道我有多喜欢做这个东西! 😀

自定义UILabel的Swift 3代码

 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) ie 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 } } 

故事板上的使用示例

文本路径使用

我所做的更改

  • 我删除了现在可以直接从标签获取的参数。
  • 我承认在三angular学中不是最聪明的,在我这个年纪忘记了很多,所以我把所有相关的定义都包括进去了,所以我可以开始理解@Grimxn的辉煌。
  • angular度顺时针设置现在是您可以在“属性”检查器中调整的属性。
  • 我现在从标签的大小创build半径。
  • 把一些标准格式的注释放在函数上,你知道,所以你得到了OPTION + CLICK函数的popup窗口。

帮助文本示例

我见过的问题

我鼓励你编辑上面的内容来改进它。

  • 我不知道为什么,但有时候,标签仍然在其他控件上进行渲染,即使它在文档大纲中位于其后面。

总是相同的实施,但调整了斯威夫特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) ie 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 } } 

@IBDesignable对于Swift 2的循环path上的UILabel

非常感谢@Grimxn和@ mark-moeykens绝对的杀手工作。 我对Mark的工作做了一个小的重构,所以我可以在一个没有花时间更新Swift 3的项目中使用它。想要分享,因为之前的post是非常有用的。

自定义UILabel的Swift 2代码

 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) ie 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 } }