I'm using a custom font and somehow the rendering screws up the line height, potentially because of misconfigured descent
or leading
(?), so that g's and j's are cut off in the last line of the rendered text. I think it might be a problem with this particular font, because Sketch is also exposing similar issues with the font in question, but I feel like I don't understand quite enough about typographic measurements or fonts. I found this Apple documentation page on Typographic Concepts quite insightful.
I looked into the font itself with the test version of FontLab, which I have used for the first time btw - so I've little clue really what I'm looking at. It does seem like the g is going below the configured descent
, which seems to be what the last line is. (?) (See: Character view in FontLab, showing the descend of the g)
Via lineSpacing
I could adjust the distance between just the lines itself to fix this in the first few lines. I know iOS 14 is going to bring a way to modify the leading
of a Text
in SwiftUI. But I need to target iOS 13, so that doesn't help.
I've also tried SwiftUI's Text
, a normal UILabel.text
and UILabel.attributedText
with a customized paragraph style, but nothing I adjust there seems to mitigate the problem.
The view is not even clipping. Just adding padding to the frame does not help at all. It increases the distance, but the g's and j's are still cut.
What can I do? Subclass UILabel
and overwrite the intrinsicContentSize
to add some extra space, when there is a g and j in the last line? That feels a) dirty and b) given that padding didn't help, it might not fix the problem?
Is the font itself the problem here? Can I patch the font somehow without making it worse?
Is there any way to modify the leading or the descend height of the font, when I use lower level APIs? Seems like I could go down to CoreText, as CTFontCreateCopyWithAttributes(_:_:_:_:)
is a candidate, if I could just modify via attributes the leading, line space or the descend? Can or monkey-patch / swizzle things without shooting myself in the knee? Should I just file a radar a feedback?
You need to use NSAttributedString
instead of String to control the line spacing of UILabel. Here is sample code
let style = NSMutableParagraphStyle()
style.lineSpacing = 20
let string = NSMutableAttributedString(string: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog")
string.addAttribute(.paragraphStyle, value: style, range: NSMakeRange(0, string.length))
let label = UILabel(frame: CGRect(x: 20, y: 100, width: 300, height: 500))
label.attributedText = string
label.numberOfLines = 0
self.view.addSubview(label)
Out put
I know what you are asking as I have faced the same issues with custom fonts. I am going to offer two solutions. In my own project I went the way of your suggestion in overriding intrinsicContentSize and adding a padding multiplier for height and width. In my case the fonts were user facing so I had a struct that held all the relevant information. FYI Chalkduster is in the system and clips. I also believe that this is all due to the font file itself.
Solution 1: Example:
struct UserFont{
var name : String
var displayName : String
var widthMultiplier : CGFloat
var heightMultiplier : CGFloat
}
Then in my UILabel I have it subclassed to use both of these metrics
@IBDesignable
class MultiplierUILabel: UILabel {
@IBInspectable var widthPaddingMultiplier : CGFloat = 1
@IBInspectable var heightPaddingMultiplier : CGFloat = 1
override var intrinsicContentSize: CGSize{
return CGSize(width: super.intrinsicContentSize.width * widthPaddingMultiplier, height: super.intrinsicContentSize.height * heightPaddingMultiplier)
}
}
This to me was the simplest implementation as I found the font and multiplier scale accordingly.
Solution 2:
You might be able to get the draw to occur slightly higher by measuring the glyph bounds and adjusting the origin y. For example this fixes the clipping on Chalkduster font that is included in the system.
@IBDesignable
class PaddingUILabel: UILabel {
override func drawText(in rect:CGRect) {
//hello
guard let labelText = text else { return super.drawText(in: rect) }
//just some breathing room
let info = boundsForAttrString(str: labelText, font: self.font!, kern: .leastNormalMagnitude)
let glyph = info.glyph
var newRect = rect
let glyphPadding = -(glyph.origin.y)
if glyphPadding - info.descent > 1 && info.descent != 0{
newRect.origin.y -= glyphPadding/2
}else{
if info.descent != 0{
newRect.origin.y += (info.descent - glyphPadding)/2
}
}
super.drawText(in: newRect)
}
func boundsForAttrString(str:String,font:UIFont,kern:CGFloat)->(glyph:CGRect,descent:CGFloat){
let attr = NSAttributedString(string: str, attributes: [.font:font,.kern:kern])
let line = CTLineCreateWithAttributedString(attr)
var ascent : CGFloat = 0
var descent : CGFloat = 0
var leading : CGFloat = 0
CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
let glyph = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds).integral
return (glyph,leading != 0 ? descent : 0)
}
}
Result of Solution 2: System
PaddingUILabel using glyph bounds
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With