I\'m completely in the dark with Core Text\'s line spacing. I\'m using NSAttributedString and I specify the following attributes on it: - kCTFontAttributeName - kCTParagraph
Another way of twerking with a NSAttributedString line position is playing with baselineOffset attribute:
let contentText = NSMutableAttributedString(
string: "I see\nI'd think it`d be both a notification and a\nplace to see past announcements\nLike a one way chat.")
contentText.addAttribute(.baselineOffset, value: 10, range: NSRange(location: 0, length: 5))
contentText.addAttribute(.baselineOffset, value: -10, range: NSRange(location: 85, length: 20))
Result:
"I see
I'd think it`d be both a notification and a
place to see past announcements
Like a one way chat."
https://stackoverflow.com/a/55876401/4683601
I'm still not 100% confident in my following statements, but it seems to make sense. Please correct me where I am wrong.
The line height (leading) refers to the distance between the baselines of successive lines of type. The baseline here can be interpreted as the imaginary line which the text sits on.
Spacing is the space between lines. The space appears after the line of text.
I ended up using the following solution to my problem:
// NOT SURE WHAT THE THEORY BEHIND THIS FACTOR IS. WAS FOUND VIA TRIAL AND ERROR.
CGFloat factor = 14.5/30.5;
CGFloat floatValues[4];
floatValues[0] = self.lineHeight * factor/(factor + 1);
floatValues[1] = self.lineHeight/(factor + 1);
floatValues[2] = self.lineHeight;
This matrix is used with the paragraph style parameter for NSAttributedString:
CTParagraphStyleSetting paragraphStyle[3];
paragraphStyle[0].spec = kCTParagraphStyleSpecifierLineSpacing;
paragraphStyle[0].valueSize = sizeof(CGFloat);
paragraphStyle[0].value = &floatValues[0];
paragraphStyle[1].spec = kCTParagraphStyleSpecifierMinimumLineHeight;
paragraphStyle[1].valueSize = sizeof(CGFloat);
paragraphStyle[1].value = &floatValues[1];
paragraphStyle[2].spec = kCTParagraphStyleSpecifierMaximumLineHeight;
paragraphStyle[2].valueSize = sizeof(CGFloat);
paragraphStyle[2].value = &floatValues[2];
CTParagraphStyleRef style = CTParagraphStyleCreate((const CTParagraphStyleSetting*) ¶graphStyle, 3);
[attributedString addAttribute:(NSString*)kCTParagraphStyleAttributeName value:(id)style range:NSMakeRange(0, [string length])];
CFRelease(style);
Hope this helps someone. I'll update this answer as I discover more relevant information.
In Swift 3:
let textFont = UIFont(name: "Helvetica Bold", size: 20)!
let textColor = UIColor(white: 1, alpha: 1) // White
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.paragraphSpacing = 20 // Paragraph Spacing
paragraphStyle.lineSpacing = 40 // Line Spacing
let textFontAttributes = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle
] as [String : Any]
There are two properties of NSParagraphStyle
that modify the height between successive text baselines in the same paragraph: lineSpacing
and lineHeightMultiple
. @Schoob is right that a lineHeightMultiple
above 1.0
adds additional space above the text, while a lineSpacing
above 0.0
adds space below the text. This diagram shows how the various dimensions are related.
To get the text to stay centred the aim is therefore to specify one in terms of the other, in such a way that any 'padding' we add by one attribute (top/bottom) is balanced by determining the other attribute's padding (bottom/top) to match. In other words, any extra space added is distributed evenly while otherwise preserving the text's existing positioning.
The nice thing is that this way you can choose which attribute you want to specify and then just determine the other:
extension UIFont
{
func lineSpacingToMatch(lineHeightMultiple: CGFloat) -> CGFloat {
return self.lineHeight * (lineHeightMultiple - 1)
}
func lineHeightMultipleToMatch(lineSpacing: CGFloat) -> CGFloat {
return 1 + lineSpacing / self.lineHeight
}
}
From here, other answers show how these two attributes can be set in an NSAttributedString
, but this should answer how the two can be related to 'centre' the text.
Objective-C
NSInteger strLength = [myString length];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
[style setLineSpacing:24];
[attString addAttribute:NSParagraphStyleAttributeName
value:style
range:NSMakeRange(0, strLength)];
Swift 5
let strLength = myString.length()
var style = NSMutableParagraphStyle()
style.lineSpacing = 24
attString.addAttribute(.paragraphStyle, value: style, range: NSRange(location: 0, length: strLength))
I made an extension for this, see below. With the extension you can just set the line height like so:
let label = UILabel()
label.lineHeight = 19
This is the extension:
// Put this in a file called UILabel+Lineheight.swift, or whatever else you want to call it
import UIKit
extension UILabel {
var lineHeight: CGFloat {
set {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = newValue
paragraphStyle.maximumLineHeight = newValue
_setAttribute(key: NSAttributedString.Key.paragraphStyle, value: paragraphStyle)
}
get {
let paragraphStyle = _getAttribute(key: NSAttributedString.Key.paragraphStyle) as? NSParagraphStyle
return paragraphStyle?.minimumLineHeight ?? 0
}
}
func _getAttribute(key: NSAttributedString.Key) -> Any? {
return attributedText?.attribute(key, at: 0, effectiveRange: .none)
}
func _setAttribute(key: NSAttributedString.Key, value: Any) {
let attributedString: NSMutableAttributedString!
if let currentAttrString = attributedText {
attributedString = NSMutableAttributedString(attributedString: currentAttrString)
} else {
attributedString = NSMutableAttributedString(string: text ?? "")
text = nil
}
attributedString.addAttribute(key,
value: value,
range: NSRange(location: 0, length: attributedString.length))
attributedText = attributedString
}
}
Notes: