Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Rotate Core Text with Swift?

I am attempting to make an analog clock using CoreGraphics.

My problem is that I do not know how to rotate the text of the numerals.

The general process of the code is as follows:

First I rotate the context, then, if applicable I draw a number; repeat.

The complete Playground

import UIKit
import Foundation
import XCPlayground

class ClockFace: UIView {
    func drawText(context: CGContextRef, text: NSString, attributes: [String: AnyObject], x: CGFloat, y: CGFloat) -> CGSize {
        CGContextTranslateCTM(context, 0, 0)
        CGContextScaleCTM(context, 1, -1)
        let font = attributes[NSFontAttributeName] as! UIFont
        let attributedString = NSAttributedString(string: text as String, attributes: attributes)

        let textSize = text.sizeWithAttributes(attributes)

        let textPath    = CGPathCreateWithRect(CGRect(x: -x, y: y + font.descender, width: ceil(textSize.width), height: ceil(textSize.height)), nil)
        let frameSetter = CTFramesetterCreateWithAttributedString(attributedString)
        let frame       = CTFramesetterCreateFrame(frameSetter, CFRange(location: 0, length: attributedString.length), textPath, nil)

        CTFrameDraw(frame, context)

        return textSize
    }

    override func drawRect(rect: CGRect) {
        let ctx: CGContext? = UIGraphicsGetCurrentContext()
        var nums: Int = 0
        for i in 0..<60 {
            CGContextSaveGState(ctx)
            CGContextTranslateCTM(ctx, rect.width / 2, rect.height / 2)
            CGContextRotateCTM(ctx, CGFloat(6.0 * Double(i) * M_PI / 180))
            CGContextMoveToPoint(ctx, 72, 0)
            if (i % 5 == 0) {
                nums++
                drawText(ctx!, text: "\(nums)", attributes: [NSForegroundColorAttributeName : UIColor.blackColor().CGColor, NSFontAttributeName : UIFont.systemFontOfSize(12)], x: 42, y: 0)
            }
            CGContextRestoreGState(ctx)
        }

    }
}
let myView = ClockFace(frame: CGRectMake(0, 0, 150, 150))
myView.backgroundColor = .lightGrayColor()
XCPShowView("", view: myView)

Hence my question, how does one rotate Core Text with Swift?

like image 282
Brandon Bradley Avatar asked Oct 24 '25 16:10

Brandon Bradley


2 Answers

These 15 lines took me over 8 hours, gobs of StackOverflow searches (many with answers that didn't/no long work!), to figure out.

Beware of solutions that use CTLineDraw. For CTLineDraw, you have to change cgContext.textMatrix to do the rotation and I got very strange/unreliable results. Generating a frame and then rotating that frame proved MUCH more reliable.

Using Swift 5.2...

func drawCenteredString(
    cgContext: CGContext,
    string: String,
    targetCenter: CGPoint,
    angleClockwise: Angle,
    uiFont: UIFont
) {
    let attrString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: uiFont])
    let textSize = attrString.size()
    let textPath = CGPath(rect: CGRect(x: CGFloat(-textSize.width/2), y: -uiFont.ascender / 2, width: ceil(textSize.width), height: ceil(textSize.height)), transform: nil)
    let frameSetter = CTFramesetterCreateWithAttributedString(attrString)
    let frame = CTFramesetterCreateFrame(frameSetter, CFRange(location: 0, length: attrString.length), textPath, nil)
    cgContext.saveGState()
    cgContext.translateBy(x: targetCenter.x, y: targetCenter.y)
    cgContext.scaleBy(x: 1, y: -1)
    cgContext.rotate(by: CGFloat(-angleClockwise.radians))
    CTFrameDraw(frame, cgContext)
    cgContext.restoreGState()
}
like image 133
Stuart Schechter Avatar answered Oct 26 '25 05:10

Stuart Schechter


You can implement CGAffineTransform via CGContextSetTextMatrix() to rotate texts. Ex:

...
let frame = CTFramesetterCreateFrame(frameSetter, CFRange(location: 0, length: attributedString.length), textPath, nil)
CGContextSetTextMatrix(context, CGAffineTransformMakeRotation(CGFloat(M_PI)))
CTFrameDraw(frame, context)
like image 41
Allen Avatar answered Oct 26 '25 05:10

Allen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!