Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
210 views
in Technique[技术] by (71.8m points)

Draw text along circular path in Swift for iOS

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?

question from:https://stackoverflow.com/questions/32771864/draw-text-along-circular-path-in-swift-for-ios

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 +/-90o 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() Output is: Output (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 +/-90o 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 = [NSAttributedS

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...