How to draw horizontally aligned NSAttributedString in Swift - ios

I am trying to draw a multi-line string such that each line is horizontally aligned to the centre of the box. I am using NSAttributedString, thus:
let paraStyle = NSMutableParagraphStyle()
paraStyle.alignment = .center
textAttrs = [
NSFontAttributeName: font!,
NSForegroundColorAttributeName: UIColor.white,
NSParagraphStyleAttributeName: paraStyle,
NSBaselineOffsetAttributeName: NSNumber(floatLiteral: 0.0)
]
Then later I draw the string using NSAttributedString draw()
let uiText = NSAttributedString(string: text, attributes: textAttrs)
let point = CGPoint(x: self.bounds.width / 2 - uiText.size().width / 2, y: self.bounds.height / 2 - uiText.size().height / 2)
uiText.draw(at: point)
But it still comes out with each line aligned to the left. How can I draw a string in ios and center the alignment.

You may need to use draw(in rect: CGRect), rather than draw(at point: CGPoint).
override func draw(_ rect: CGRect) {
let paraStyle = NSMutableParagraphStyle()
paraStyle.alignment = .center
let textAttrs = [
NSFontAttributeName: UIFont.systemFont(ofSize: 17),
NSForegroundColorAttributeName: UIColor.blue,
NSParagraphStyleAttributeName: paraStyle,
NSBaselineOffsetAttributeName: NSNumber(floatLiteral: 0.0)
]
let text = "The American student who was released last week after being held in captivity for more than 15 months in North Korea has died, his family says. Otto Warmbier, 22, returned to the US last Tuesday, but it emerged he had been in a coma for a year.North Korea said botulism led to the coma, but a team of US doctors who assessed him dispute this account.Mr Warmbier was sentenced to 15 years of hard labour for attempting to steal a propaganda sign from a hotel."
let uiText = NSAttributedString(string: text, attributes: textAttrs)
uiText.draw(in: rect)
layer.borderColor = UIColor.black.cgColor
layer.borderWidth = 2
}

Related

How to get the height of a paragraph using PDFKit

I am writing a pdf using iOS PDFKit. Typically I can get the height of a single text item such as a title by doing the following:
return titleStringRect.origin.y + titleStringRect.size.height
Where titleStringRect is the CGRect containing the string. The returned value is the y-coordinate for the bottom of that text so that I know where to start writing the next line of text.
I have not found a way to know where a paragraph ends. The solutions I have found have been to just make a big enough CGRect that the paragraph will definitely fit in.
I need to know exactly what the height of the CGRect should be based on the String that will be written into it. Here is my code:
func addParagraph(pageRect: CGRect, textTop: CGFloat, text: String) {
let textFont = UIFont(name: "Helvetica", size: 12)
let backupFont = UIFont.systemFont(ofSize: 12, weight: .regular)
// Set paragraph information. (wraps at word breaks)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .natural
paragraphStyle.lineBreakMode = .byWordWrapping
// Set the text attributes
let textAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: textFont ?? backupFont
]
let attributedText = NSAttributedString(
string: text,
attributes: textAttributes
)
let textRect = CGRect(
x: 50.0,
y: textTop,
width: pageRect.width - 100,
height: pageRect.height - textTop - pageRect.height / 5.0
)
attributedText.draw(in: textRect)
}
As you can see the above code just makes a CGRect that is 1/5th of the space below the previous text regardless of how many lines the paragraph will actually be.
I have tried averaging the character count per line in order to estimate how many lines the paragraph will be but this is unreliable and definitely a hack.
What I need is for the addParagraph function to return the y-coordinate for the bottom of the paragraph so that I know where to start writing the next piece of content.
I ended up finding the solution to this and it is pretty simple. I'll post the code and then explain it for anyone else who has this problem.
let paragraphSize = CGSize(width: pageRect.width - 100, height: pageRect.height)
let paragraphRect = attributedText.boundingRect(with: paragraphSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
First define a CGSize that is a certain width and height. Set the width to the width you want the paragraph to be and set the height to a large value that will fit the content. Then call
attributedText.boundingRect(with: paragraphSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
Where attributedText is the paragraph content. The boundingRect method returns a CGRect which is the size required to fit the content into, but no more. Now you can return the bottom of the paragraph. This method will not change the width unless it cannot fit the String into the height you provided. For my purpose this was perfect. Here is the full code:
func addParagraph(pageRect: CGRect, textTop: CGFloat, paragraphText: String) -> CGFloat {
let textFont = UIFont(name: "Helvetica", size: 12)
let backupFont = UIFont.systemFont(ofSize: 12, weight: .regular)
// Set paragraph information. (wraps at word breaks)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .natural
paragraphStyle.lineBreakMode = .byWordWrapping
// Set the text attributes
let textAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: textFont ?? backupFont
]
let attributedText = NSAttributedString(
string: paragraphText,
attributes: textAttributes
)
// determine the size of CGRect needed for the string that was given by caller
let paragraphSize = CGSize(width: pageRect.width - 100, height: pageRect.height)
let paragraphRect = attributedText.boundingRect(with: paragraphSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
// Create a CGRect that is the same size as paragraphRect but positioned on the pdf where we want to draw the paragraph
let positionedParagraphRect = CGRect(
x: 50,
y: textTop,
width: paragraphRect.width,
height: paragraphRect.height
)
// draw the paragraph into that CGRect
attributedText.draw(in: positionedParagraphRect)
// return the bottom of the paragraph
return positionedParagraphRect.origin.y + positionedParagraphRect.size.height
}

Vertically aligning NSTextAttachment in NSMutableAttributedString

I'm adding an icon to a UILabel using NSTextAttachment inside an NSMutableAttributedString like this:
//Setting up icon
let moneyIcon = NSTextAttachment()
moneyIcon.image = UIImage(named: "MoneyIcon")
let moneyIconString = NSAttributedString(attachment: moneyIcon)
//Setting up text
let balanceString = NSMutableAttributedString(string: " 1,702,200")
balanceString.insert(moneyIconString, at: 0)
//Adding string to label
self.attributedText = balanceString
self.sizeToFit()
But for some reason the icon isn't vertically aligned
Does anybody know how can I align it?
Thank you!
use bounds property of NSTextAttachment.
//Setting up icon
let moneyIcon = NSTextAttachment()
moneyIcon.image = UIImage(named: "MoneyIcon")
let imageSize = moneyIcon.image!.size
moneyIcon.bounds = CGRect(x: CGFloat(0), y: (font.capHeight - imageSize.height) / 2, width: imageSize.width, height: imageSize.height)
let moneyIconString = NSAttributedString(attachment: moneyIcon)
//Setting up text
let balanceString = NSMutableAttributedString(string: " 1,702,200")
balanceString.insert(moneyIconString, at: 0)
//Adding string to label
self.attributedText = balanceString
self.sizeToFit()
This answer, which is about vertically centering two differently sized fonts in a single NSAttributedString, mentions using the baseline offset to calculate the center of the string.
You can use the same approach when using an image:
Subtract the font size from the image's height and divide it by 2.
Subtract the font's descender from the value (since font size isn't the same as the ascent of your font). The font that you are particularly using (Baloo-Regular) has a descender value that differs from the standard and it should be divided by 2. Other fonts (including San Fransisco) don't need that fix or require a different divisor.
This code covers most cases, if your font behaves differently, you should check out the guide for managing texts in Text Kit.
// *Setting up icon*
let moneyIcon = NSTextAttachment()
// If you're sure a value is not and will never be nil, you can use "!".
// Otherwise, avoid it.
let moneyImage = UIImage(named: "MoneyIcon")!
moneyIcon.image = moneyImage
let moneyIconString = NSAttributedString(attachment: moneyIcon)
// *Setting up NSAttributedString attributes*
let balanceFontSize: CGFloat = 16
let balanceFont = UIFont(name: "Baloo", size: balanceFontSize)!
let balanceBaselineOffset: CGFloat = {
let dividend = moneyImage.size.height - balanceFontSize
return dividend / 2 - balanceFont.descender / 2
}()
let balanceAttr: [NSAttributedString.Key: Any] = [
.font: balanceFont,
.baselineOffset: balanceBaselineOffset
]
// *Setting up text*
let balanceString = NSMutableAttributedString(
string: " 1,702,200",
attributes: balanceAttr
)
balanceString.insert(moneyIconString, at: 0)

Formatting text for chord and song lyrics Textview or UiLabel Swift

could anyone give me some tips on how to implement in a textview or label, formatting text for chord and song lyrics? Kind of image below?
you have the same challenge as mine. I'm thinking about drawing text on a CATextLayer object every time i found [G] or [Em] in my lyric text like: "[G]I read the [Bm]news today, oh [Em]boy, [Em7]"
func drawLayer(_ chord: String, frame: CGRect) {
let textLayer = CATextLayer()
textLayer.frame = frame
let myAttributes = [
NSFontAttributeName: UIFont(name: "Helvetica", size: 13.0) , // font
NSForegroundColorAttributeName: UIColor.cyan // text color
]
let myAttributedString = NSAttributedString(string: chord, attributes: myAttributes )
textLayer.string = myAttributedString
lyricView.layer.addSublayer(textLayer)
}
The difficult part is to determine the position of every single chord, you must specify it's position exactly at the top of your lyric. (i'm still searching the best method to get the x & y position)

howto calculate the size of a CGRect to fit with a text ( IOS , swift )

I draw a text in my view and this works fine.
Now how can I calculate the width of my text, which is drawn ?
My code :
In my code I have defined "iBoxLength", which is the assumed size of the text and is well-proportioned (too large) .
let fieldColor: UIColor = UIColor.darkGrayColor()
let fieldFont = UIFont(name: "Helvetica Neue", size: fTextSize)
var paraStyle = NSMutableParagraphStyle()
//var textalign = NSTextAlignment.Center;
paraStyle.lineSpacing = 6.0
paraStyle.alignment = NSTextAlignment.Center;
var skew = 0.1
let attributes = [
NSForegroundColorAttributeName: fieldColor,
NSParagraphStyleAttributeName: paraStyle,
NSObliquenessAttributeName: skew,
//NSTextAlignment: textalign,
NSFontAttributeName: fieldFont!
]
s.drawInRect(CGRectMake(CGFloat(Float(iTextPositionX)-Float(iBoxLength/2)), CGFloat(iTextPositionY), CGFloat(iBoxLength), CGFloat(10)), withAttributes: attributes)
var Textwidth:Float = getWidth(s); // <------------------------------
NSString has a sizeWithAttributes() method:
let text = NSString("myText")
let textSize = text.sizeWithAttributes(attributes)
See: NSString UIKit Additions Reference

Draw a text with center alignment in swift not working as expected

I have a Text "SUGUS" which I want to draw at point 100,100 with Textalignment=center. So I expect to have the Character "G" at the point 100,100 , because I like to center it.
Does someone know what I am doing wrong regarding following code ?
var s: NSString=NSString(string: "SUGUS");
var iTextPositionX:Int = 100;
var iTextPositionY:Int = 100;
let fieldColor: UIColor = UIColor.darkGrayColor()
let fieldFont = UIFont(name: "Helvetica Neue", size: 12)
var paraStyle = NSMutableParagraphStyle()
paraStyle.lineSpacing = 6.0
paraStyle.alignment = NSTextAlignment.Center;// <---
var skew = 0.1
let attributes = [
NSForegroundColorAttributeName: fieldColor,
NSParagraphStyleAttributeName: paraStyle,
NSObliquenessAttributeName: skew,
//NSTextAlignment: textalign,
NSFontAttributeName: fieldFont!
]
var Textpoint:CGPoint=CGPoint(x: iTextPositionX, y: iTextPositionY);
s.drawAtPoint(Textpoint, withAttributes: attributes);

Resources