How to Rotate Core Text in Swift? - ios

So I have the following code that rotates the CGContext to position where my text should be:
var nums: Int = 0
let oNums: [Int] = [3,4,5,6,7,8,9,10,11,12,1,2]
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))
CGContextSetStrokeColorWithColor(ctx, UIColor.grayColor().CGColor)
CGContextMoveToPoint(ctx, 72, 0)
CGContextSetLineWidth(ctx, 3.0)
CGContextAddLineToPoint(ctx, 42, 0)
drawText(ctx!, text: getRoman(oNums[nums]), attributes: [NSForegroundColorAttributeName : UIColor.blackColor().CGColor, NSFontAttributeName : UIFont.systemFontOfSize(12)], x: 42, y: 0, align: CGFloat(6.0 * Double(i) * M_PI / 180))
nums++
}
Where drawText(_:) is:
func drawText(context: CGContextRef, text: NSString, attributes: [String: AnyObject], x: CGFloat, y: CGFloat, align: 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
}
And getRoman(_:) is:
func getRoman(numeral: Int) -> String {switch numeral {case 1:return "I"case 2:return "II"case 3:return "III"case 4:return "IV"case 5:return "V"case 6:return "VI"case 7:return "VII"case 8:return "VIII"case 9:return "IX"case 10:return "X"case 11:return "XI"case 12:return "XII"default:return ""}}
The issue, is that this prints text rotated to an angle (the desired result would be text all angled to the same degree (standard text orientation))
Is there a way to rotate Core Text?
Regards, Brandon

Related

TextAttribute not getting applied

I have 3 function where Im drawing text on Image. The text includes attribute paragraphStyle = .right, but the text always getting aligned to left.
These are my function :
Function Name drawText() :
private func drawText(file: String = #file , line: Int = #line, function: String = #function) {
var myInflectionRate = inflectionRate
let EPS = 0.00000001
if myInflectionRate >= 0 {
myInflectionRate = max(EPS,myInflectionRate)
}else if myInflectionRate < 0{
myInflectionRate = min(-EPS,myInflectionRate)
}
let attributedText = NSAttributedString(string: text, attributes: textAttributes)
var rect = attributedText.boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let nsText = text as NSString
let textSize = nsText.size(withAttributes: textAttributes)
let angle = myInflectionRate*2*Double.pi
let arcLength = textSize.width
let r = arcLength / angle
let min_r = arcLength/(2*Double.pi)
let angleB = (Double.pi - angle) / 2.0
let widthOfRect = max(abs(angle) < Double.pi ? r * (sin(angle)/sin(angleB)) : abs(2*r), 2.0*min_r)
let positiveR = abs(r)
let positiveAngle = abs(angle)
let heightOfRect = max( positiveAngle < Double.pi ? positiveR - sqrt(positiveR*positiveR - (widthOfRect/2)*(widthOfRect/2)) : positiveR + positiveR*sin((positiveAngle-Double.pi)/2.0), 2.0*0)
rect.size = CGSize(width: widthOfRect + textSize.height, height: heightOfRect + 2.0*textSize.height)
if text.isEmpty {
rect.size = CGSize(width: 80, height: 80)
}
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
if myInflectionRate >= EPS {
context.translateBy (x: rect.size.width / 2, y: textSize.height + r )
}else if myInflectionRate <= -EPS{
context.translateBy (x: rect.size.width / 2, y: rect.size.height - textSize.height + r )
}else{
context.translateBy (x: rect.size.width / 2, y: rect.size.height/2)
}
context.scaleBy(x: 1, y: -1)
context.setFillColor(UIColor.clear.cgColor)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
imageViewForText?.image = image
imageViewForText?.contentMode = .scaleAspectFit
imageViewForText?.frame = self.bounds.insetBy(dx: globalInset!, dy: globalInset!)
imageViewForText?.backgroundColor = textBackgroundColor?.withAlphaComponent(textBackgroundAlpha ?? 1)
refresh()
}
Function Name centreArcPerpendicular() :
func centreArcPerpendicular(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, attributes: [NSAttributedString.Key: AnyObject], clockwise: Bool, size: CGSize){
let characters: [String] = str.map { String($0) } // An array of single character strings, each character in str
let l = characters.count
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: r)]
totalArc += arcs[i]
}
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection: CGFloat = clockwise ? -.pi / 2 : .pi / 2
var thetaI = theta - direction * totalArc / 2
var modifiedAttributes = attributes
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
let ColorArraySize = CurrentTextConfiguration.shared.textConfiguration.textureTextColor.count
let offset = characters[i].size(withAttributes: attributes)
modifiedAttributes[NSAttributedString.Key.foregroundColor] = CurrentTextConfiguration.shared.textConfiguration.textureTextColor[i%ColorArraySize].getColor(bounds: CGRect(origin: .zero, size: offset))
thetaI += direction * arcs[i] / 2
}
centre(text: str, context: context, radius: r, angle: theta, attributes: modifiedAttributes, slantAngle: thetaI + slantCorrection,size : size)
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
if (chord/(2*radius)) > 1 {
return 2 * asin(1)
}else if (chord/(2*radius)) < -1 {
return 2 * asin(-1)
}
return 2 * asin(chord / (2 * radius))
}
Function name center() :
func centre(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, attributes: [NSAttributedString.Key: AnyObject], slantAngle: CGFloat, size : CGSize) {
NSAttributedString.Key.font: font]
context.saveGState()
context.scaleBy(x: 1, y: -1)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
context.rotate(by: -slantAngle)
let offset = str.size(withAttributes: attributes)
context.translateBy (x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
context.restoreGState()
}
I want to align my text from right , not left

When using UIGraphicsBeginImageContextWithOptions, the text color cannot be adjusted

hi i am a beginner in speed writing
I refer to this writing method and apply it in my segment controller
I am using swift 5.7 method
This can be successfully used and executed with
But i have a problem that i can't change the color of my text and icon
I try to add something in "string.draw" but it dosen't work
The method I use is as follows
class func textEmbededImage(image: UIImage, string: String, color: UIColor, imageAlignment: Int = 0) -> UIImage {
let font = UIFont.systemFont(ofSize: 13.0)
let expectedTextSize: CGSize = (string as NSString).size(withAttributes: [NSAttributedString.Key.font: font])
let width: CGFloat = expectedTextSize.width + image.size.width + 5.0
let height: CGFloat = max(expectedTextSize.height, image.size.width)
let size: CGSize = CGSize(width: width, height: height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
let context: CGContext = UIGraphicsGetCurrentContext()!
context.setFillColor(color.cgColor)
let fontTopPosition: CGFloat = (height - expectedTextSize.height) / 2.0
let textOrigin: CGFloat = (imageAlignment == 0) ? image.size.width + 5 : 0
let textPoint: CGPoint = CGPoint.init(x: textOrigin, y: fontTopPosition)
string.draw(at: textPoint, withAttributes: [.font : font , .foregroundColor : UIColor.gray])
let flipVertical: CGAffineTransform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height)
context.concatenate(flipVertical)
let alignment: CGFloat = (imageAlignment == 0) ? 0.0 : expectedTextSize.width + 5.0
context.draw(image.cgImage!, in: CGRect.init(x: alignment, y: ((height - image.size.height) / 2.0), width: image.size.width, height: image.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext(
return newImage!
}
and i use like this
customSegment.setImage(UIImage.textEmbededImage(image: UIImage.add, string: "QQQ", color: .red),forSegmentAt: 0)
customSegment.setImage(UIImage.textEmbededImage(image: UIImage.add, string: "AAA", color: .white),forSegmentAt: 1)
customSegment.setImage(UIImage.textEmbededImage(image: UIImage.add, string: "BBB", color: .red),forSegmentAt: 2)
but this doesn't work for
I went into debug view hierarchy xcode to try to find the problem
debug view hierarchy
But what puzzles me even more is that what I see on the right layer is the gray color I set, but what I see on the screen is the blue color set by the system
How can I solve it
In addition, I also want to ask, how to make different text colors to achieve the effect when selecting

Rotate Text in Swift

I am trying to draw a few strings in a custom view with swift. I have done the same thing on Android with Java, and here's the result:
With Swift, I get this graphic:
Here's my code:
let π:CGFloat = CGFloat(M_PI)
extension NSString {
func drawWithBasePoint(basePoint: CGPoint, andAngle angle: CGFloat, andAttributes attributes: [String: AnyObject]) {
let textSize: CGSize = self.sizeWithAttributes(attributes)
let context: CGContextRef = UIGraphicsGetCurrentContext()!
let t: CGAffineTransform = CGAffineTransformMakeTranslation(basePoint.x, basePoint.y)
let r: CGAffineTransform = CGAffineTransformMakeRotation(angle)
CGContextConcatCTM(context, t)
CGContextConcatCTM(context, r)
self.drawAtPoint(CGPointMake(textSize.width / 2, -textSize.height / 2), withAttributes: attributes)
CGContextConcatCTM(context, CGAffineTransformInvert(r))
CGContextConcatCTM(context, CGAffineTransformInvert(t))
}
}
override func drawRect(rect: CGRect) {
elements = delegate!.getRotaryElements(self)
let center = CGPoint(x:bounds.width / 2, y: bounds.height / 2)
let sectorSize = 2 * π / CGFloat.init(elements.count)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = NSTextAlignment.Center
for i in 0...elements.count - 1 {
let attrs = [NSFontAttributeName: UIFont(name: "HelveticaNeue-Thin", size: 14)!, NSParagraphStyleAttributeName: paragraphStyle, NSForegroundColorAttributeName:(i % 2 == 0 ? UIColor.redColor():primaryColor)]
elements[i].drawWithBasePoint(center, andAngle: CGFloat(i) * sectorSize + (sectorSize / 2), andAttributes: attrs)
}
}
How can I produce the same thing in Swift? I am new to Swift (and iOS for that matter) and would appreciate any hint you can give me.
Try the code below, and to see if it works.
let π:CGFloat = CGFloat(M_PI)
extension NSString {
func drawWithBasePoint(basePoint: CGPoint, andAngle angle: CGFloat, andAttributes attributes: [String: AnyObject]) {
let radius: CGFloat = 100
let textSize: CGSize = self.sizeWithAttributes(attributes)
let context: CGContextRef = UIGraphicsGetCurrentContext()!
let t: CGAffineTransform = CGAffineTransformMakeTranslation(basePoint.x, basePoint.y)
let r: CGAffineTransform = CGAffineTransformMakeRotation(angle)
CGContextConcatCTM(context, t)
CGContextConcatCTM(context, r)
self.drawAtPoint(CGPointMake(radius-textSize.width/2, -textSize.height/2), withAttributes: attributes)
CGContextConcatCTM(context, CGAffineTransformInvert(r))
CGContextConcatCTM(context, CGAffineTransformInvert(t))
}
}
In Swift 5.0:
extension String {
func drawWithBasePoint(basePoint: CGPoint,
andAngle angle: CGFloat,
andAttributes attributes: [NSAttributedString.Key : Any]) {
let textSize: CGSize = self.size(withAttributes: attributes)
let context: CGContext = UIGraphicsGetCurrentContext()!
let t: CGAffineTransform = CGAffineTransform(translationX: basePoint.x, y: basePoint.y)
let r: CGAffineTransform = CGAffineTransform(rotationAngle: angle)
context.concatenate(t)
context.concatenate(r)
self.draw(at: CGPoint(x: textSize.width / 2, y: -textSize.height / 2), withAttributes: attributes)
context.concatenate(r.inverted())
context.concatenate(t.inverted())
}
}

Core Text Rotation does not properly rotate - Swift

I am attempting to rotate Core Text such that it is readable despite it being generated via a rotated context.
My issue is that regardless of the rotation method, the resulting text is not what is expected.
Here is the working playground:
import Foundation
import UIKit
import XCPlayground
class ClockFace: UIView {
func drawText(context: CGContextRef, text: NSString, attributes: [String: AnyObject], x: CGFloat, y: CGFloat, align: 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)
CGContextSetTextMatrix(context, CGAffineTransformMakeRotation(align * 43.0))
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))
if (i % 5 == 0) {
nums++
drawText(ctx!, text: "\(nums)", attributes: [NSForegroundColorAttributeName : UIColor.blackColor().CGColor, NSFontAttributeName : UIFont.systemFontOfSize(12)], x: 42, y: 0, align: CGFloat(6.0 * Double(i) * M_PI / 180))
}
CGContextRestoreGState(ctx)
}
}
}
let myView = ClockFace(frame: CGRectMake(0, 0, 150, 150))
myView.backgroundColor = .lightGrayColor()
XCPShowView("", view: myView)
Regardless of the value in CGAffineTransfomMakeRotation the text is illegible.
Why is this? Am I rotating the text correctly?
Regards, Brandon
I draw a clock face based on your codes but revised little bit.
Basically what I did is to flip and move whole clock face outside the for-loop. You can see I moved CGContextTranslateCTM() and CGContextScaleCTM() from drawText() to drawRect(). Others are some minor calculation changes owing to above modification.
As to CGContextSetTextMatrix(), the rotation happens here is affecting each character but not whole string. You can comment line 19 & 20 and uncomment line 21 to have a different result and see what I mean.
Also if you would like to get the playground file directly, you can download it from here.
import Foundation
import UIKit
import XCPlayground
class ClockFace: UIView {
func drawText(context: CGContextRef, text: String, attributes: [String: AnyObject], x: CGFloat, y: CGFloat, align: CGFloat) -> CGSize {
let font = attributes[NSFontAttributeName] as! UIFont
let attributedString = NSAttributedString(string: text, 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)
var transform = CGAffineTransformMakeTranslation(x + 10, y + font.descender)
transform = CGAffineTransformRotate(transform, align)
//CGContextSetTextMatrix(context, CGAffineTransformMakeRotation(align))
CGContextConcatCTM(context, transform);
CTFrameDraw(frame, context)
return textSize
}
override func drawRect(rect: CGRect) {
let ctx: CGContext? = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(ctx, -45, rect.size.height + 5)
CGContextScaleCTM(ctx, 1, -1)
var nums: Int = 2
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))
if (i % 5 == 0) {
nums++
nums = nums > 12 ? nums - 12 : nums
drawText(ctx!, text: "\(nums)", attributes: [NSForegroundColorAttributeName : UIColor.whiteColor(), NSFontAttributeName : UIFont.systemFontOfSize(12)], x: 42, y: 0, align: CGFloat(6.0 * Double(i) * M_PI / 180))
}
CGContextRestoreGState(ctx)
}
}
}
let view = ClockFace(frame: CGRectMake(0, 0, 150, 150))
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
XCPlaygroundPage.currentPage.liveView = view

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?
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()
}
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)

Resources