Created
January 22, 2017 22:03
-
-
Save cemolcay/9525d28a203da83d333545fc6e0d4371 to your computer and use it in GitHub Desktop.
Draws a curved string on a CALayer with angle, radius and text that you give.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
// swift port of stackoverflow answer | |
// http://stackoverflow.com/a/31301238/2048130 | |
extension CGFloat { | |
/** Degrees to Radian **/ | |
var degrees: CGFloat { | |
return self * (180.0 / .pi) | |
} | |
/** Radians to Degrees **/ | |
var radians: CGFloat { | |
return self / 180.0 * .pi | |
} | |
} | |
func drawCurvedString(on layer: CALayer, text: NSAttributedString, angle: CGFloat, radius: CGFloat) { | |
var radAngle = angle.radians | |
let textSize = text.boundingRect( | |
with: CGSize(width: .max, height: .max), | |
options: [.usesLineFragmentOrigin, .usesFontLeading], | |
context: nil) | |
.integral | |
.size | |
let perimeter: CGFloat = 2 * .pi * radius | |
let textAngle: CGFloat = textSize.width / perimeter * 2 * .pi | |
var textRotation: CGFloat = 0 | |
var textDirection: CGFloat = 0 | |
if angle > CGFloat(10).radians, angle < CGFloat(170).radians { | |
// bottom string | |
textRotation = 0.5 * .pi | |
textDirection = -2 * .pi | |
radAngle += textAngle / 2 | |
} else { | |
// top string | |
textRotation = 1.5 * .pi | |
textDirection = 2 * .pi | |
radAngle -= textAngle / 2 | |
} | |
for c in 0..<text.length { | |
let letter = text.attributedSubstring(from: NSRange(c..<c+1)) | |
let charSize = letter.boundingRect( | |
with: CGSize(width: .max, height: .max), | |
options: [.usesLineFragmentOrigin, .usesFontLeading], | |
context: nil) | |
.integral | |
.size | |
let letterAngle = (charSize.width / perimeter) * textDirection | |
let x = radius * cos(radAngle + (letterAngle / 2)) | |
let y = radius * sin(radAngle + (letterAngle / 2)) | |
let singleChar = drawText( | |
on: layer, | |
text: letter, | |
frame: CGRect( | |
x: (layer.frame.size.width / 2) - (charSize.width / 2) + x, | |
y: (layer.frame.size.height / 2) - (charSize.height / 2) + y, | |
width: charSize.width, | |
height: charSize.height)) | |
layer.addSublayer(singleChar) | |
singleChar.transform = CATransform3DMakeAffineTransform(CGAffineTransform(rotationAngle: radAngle - textRotation)) | |
radAngle += letterAngle | |
} | |
} | |
func drawText(on layer: CALayer, text: NSAttributedString, frame: CGRect) -> CATextLayer { | |
let textLayer = CATextLayer() | |
textLayer.frame = frame | |
textLayer.string = text | |
textLayer.alignmentMode = kCAAlignmentCenter | |
textLayer.contentsScale = UIScreen.main.scale | |
return textLayer | |
} | |
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) | |
drawCurvedString( | |
on: view.layer, | |
text: NSAttributedString( | |
string: "This is a test string", | |
attributes: [ | |
NSForegroundColorAttributeName: UIColor.white, | |
NSFontAttributeName: UIFont.systemFont(ofSize: 15) | |
]), | |
angle: 0, | |
radius: 85) | |
Thanks for sharing, i would like to ask how to control the drawing path (shape) with given value like, if given value ia 0 it should draw straight line and if the value is negative it should start to bending string from bottom to top and vice versa?
Thanks for sharing,
I would like to share one maybe bug maybe feature :),
line 34
if angle > CGFloat(10).radians, angle < CGFloat(170).radians {
I think this gonna be always "false"
Instead of angle in degree we should use angle in radians so we can have rotated string in the bottom half
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is nice, thanks for sharing.
One thing, the notes next to your 'degrees' and 'radian' computed properties are backwards. The "radians to degrees" is degrees to radians.