Advertisement
Don_Mag

Curved Text Example

Sep 14th, 2022
1,383
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 5.30 KB | None | 0 0
  1. class ViewController: UIViewController {
  2.    
  3.     override func viewDidLoad() {
  4.         super.viewDidLoad()
  5.        
  6.         let testLabel = UILabelX()
  7.         testLabel.translatesAutoresizingMaskIntoConstraints = false
  8.         view.addSubview(testLabel)
  9.        
  10.         let g = view.safeAreaLayoutGuide
  11.        
  12.         NSLayoutConstraint.activate([
  13.            
  14.             // constrain original image view Top / Leading / Trailing
  15.             testLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
  16.             testLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
  17.             testLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
  18.             // let's use the image's aspect ratio
  19.             testLabel.heightAnchor.constraint(equalToConstant: 300.0),
  20.            
  21.         ])
  22.  
  23.         testLabel.backgroundColor = .yellow
  24.         testLabel.text = "This is a test of the UILabelX subclass."
  25.        
  26.     }
  27.    
  28. }
  29.  
  30. // from: https://stackoverflow.com/a/69056167/6257435
  31. @IBDesignable
  32. class UILabelX: UILabel {
  33.    
  34.     @IBInspectable var angle: CGFloat = 1.6
  35.     @IBInspectable var clockwise: Bool = true
  36.    
  37.     override func draw(_ rect: CGRect) {
  38.         centreArcPerpendicular()
  39.     }
  40.    
  41.     /**
  42.      This draws the self.text around an arc of radius r,
  43.      with the text centred at polar angle theta
  44.      */
  45.     func centreArcPerpendicular() {
  46.         guard let context = UIGraphicsGetCurrentContext() else { return }
  47.         let str = self.text ?? ""
  48.         let size = self.bounds.size
  49.         context.translateBy(x: size.width / 2, y: size.height / 2)
  50.        
  51.         let radius = getRadiusForLabel()
  52.         let l = str.count
  53.         //        let attributes: [String : Any] = [NSAttributedString.Key: self.font]
  54.         let attributes : [NSAttributedString.Key : Any] = [.font : self.font]
  55.        
  56.         let characters: [String] = str.map { String($0) } // An array of single character strings, each character in str
  57.         var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
  58.         var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
  59.        
  60.         // Calculate the arc subtended by each letter and their total
  61.         for i in 0 ..< l {
  62.             //            arcs = [chordToArc(characters[i].widthOfString(usingFont: self.font), radius: radius)]
  63.             arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: radius)]
  64.             totalArc += arcs[i]
  65.         }
  66.        
  67.         // Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
  68.         // or anti-clockwise (right way up at 6 o'clock)?
  69.         let direction: CGFloat = clockwise ? -1 : 1
  70.         let slantCorrection = clockwise ? -CGFloat(Double.pi/2) : CGFloat(Double.pi/2)
  71.        
  72.         // The centre of the first character will then be at
  73.         // thetaI = theta - totalArc / 2 + arcs[0] / 2
  74.         // But we add the last term inside the loop
  75.         var thetaI = angle - direction * totalArc / 2
  76.        
  77.         for i in 0 ..< l {
  78.             thetaI += direction * arcs[i] / 2
  79.             // Call centre with each character in turn.
  80.             // Remember to add +/-90º to the slantAngle otherwise
  81.             // the characters will "stack" round the arc rather than "text flow"
  82.             centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
  83.             // The centre of the next character will then be at
  84.             // thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
  85.             // but again we leave the last term to the start of the next loop...
  86.             thetaI += direction * arcs[i] / 2
  87.         }
  88.     }
  89.    
  90.     func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
  91.         // *******************************************************
  92.         // Simple geometry
  93.         // *******************************************************
  94.         return 2 * asin(chord / (2 * radius))
  95.     }
  96.    
  97.     /**
  98.      This draws the String str centred at the position
  99.      specified by the polar coordinates (r, theta)
  100.      i.e. the x= r * cos(theta) y= r * sin(theta)
  101.      and rotated by the angle slantAngle
  102.      */
  103.     func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
  104.         // Set the text attributes
  105.         let attributes = [NSAttributedString.Key.font: self.font!] as [NSAttributedString.Key : Any]
  106.         // Save the context
  107.         context.saveGState()
  108.         // Move the origin to the centre of the text (negating the y-axis manually)
  109.         context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
  110.         // Rotate the coordinate system
  111.         context.rotate(by: -slantAngle)
  112.         // Calculate the width of the text
  113.         let offset = str.size(withAttributes: attributes)
  114.         // Move the origin by half the size of the text
  115.         context.translateBy(x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
  116.         // Draw the text
  117.         str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
  118.         // Restore the context
  119.         context.restoreGState()
  120.     }
  121.    
  122.     func getRadiusForLabel() -> CGFloat {
  123.         // Imagine the bounds of this label will have a circle inside it.
  124.         // The circle will be as big as the smallest width or height of this label.
  125.         // But we need to fit the size of the font on the circle so make the circle a little
  126.         // smaller so the text does not get drawn outside the bounds of the circle.
  127.         let smallestWidthOrHeight = min(self.bounds.size.height, self.bounds.size.width)
  128.         let heightOfFont = self.text?.size(withAttributes: [NSAttributedString.Key.font: self.font]).height ?? 0
  129.        
  130.         // Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
  131.         return (smallestWidthOrHeight/2) - heightOfFont + 5
  132.     }
  133. }
  134.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement