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()
输出为:x1c 0d1x
更新增加了顺时针/逆时针和直线示例。 更新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()
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()
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
}
}
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
}
}
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
}
}
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
}
}
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()
7条答案
按热度按时间i2loujxw1#
本来想说“你都试过什么了",但现在是周五下午,我下班早,所以就趁机翻译了一下我的老ObjC代码,这就是了,适合Playground,放在你的UIView里应该是小事一桩。
雨燕2
Swift 3和Swift 4更新见下文...
输出为:x1c 0d1x
更新增加了顺时针/逆时针和直线示例。
更新Swift 3
雨燕4
再一次,小的变化,这一次修复了
M_PI
的弃用,String
放弃了.characters
,.size(withAttributes...
中的参数标签的变化,以及NSAttributedStringKey
枚举的文本属性的变化。更新以显示在UIView中的使用
评论员@RitvikUpadhyaya问如何在
UIView
中实现这一点--这对老手来说是显而易见的,但对初学者来说可能就不那么容易了。诀窍是使用UIGraphicsGetCurrentContext
* 而不 * 调用UIGraphicsBeginImageContextWithOptions
(这会将UIView
的上下文覆盖为 * 当前 * 上下文)来获得正确的上下文--因此你的UIView
应该看起来像这样:hivapdat2#
@ IB可用于循环路径上的UI标签
首先,我想我们都同意@Grimxn是那个人!他的解决方案很棒。我把他的工作和重构成一个自定义的UILabel控件,你可以在故事板上设置和编辑。如果你们看过我的视频,你会知道我有多喜欢做这些事情!😀
自定义UI标签的Swift 3代码
情节提要上的用法示例
我所做的更改
我遇到的问题
我鼓励你编辑上面的内容来改进它。
oxf4rvwz3#
始终采用相同的实施方式,但针对Swift 4进行了调整
xmd2e60i4#
@可为Swift 2循环路径上的UI标签设计
非常感谢@Grimxn和@mark-moeykens的工作。我对Mark的工作做了一个小的重构,这样我就可以在一个还没有花时间更新到Swift 3的项目中使用它。我想分享一下,因为以前的帖子很有帮助。
自定义UI标签的Swift 2代码
6qfn3psc5#
基于Grimxm代码的C#版本:
适用于Xamarin iOS应用程序。
83qze16e6#
将代码更新为Swift 5
v6ylcynt7#
完整的Playground代码