Fix line spacing in custom font in SwiftUI

Viewed 2327

I am using custom font (Catamaran) and it seems like it has big space between lines in it. For example I have this code:

Text("Example text that has big space between lines")
    .lineSpacing(0)
    .padding(.horizontal)
    .font(Font.custom(FontNameManager.Catamaran.bold, size: 24.0))
    .foregroundColor(.white)
    .multilineTextAlignment(.center)

and it looks like this:

Screenshot with text example from previous code

As you can see it doesn't have zero space between lines but it still gets too much space. I even tried to set negative numbers to lineSpacing method but it doesn't help. What can I do with this? How I can fix it? In UIKit I would probably use attributed string and I think I can use UILabel as UIViewRepresentable and then I can use attributed string in SwiftUI iOS 14. Is there some easier solution which can "fix" font for any usage? Do I have to edit original .ttf file? Why there is this space between lines in this font?

Thanks for any help

2 Answers

SwiftUI might use values of hhea (Horizontal header table) to set the Text box height. In your case, Catamaran has an ascender of 1100 and a descender of 540. The box height will be calculated as the sum of these two values: 540 + 1100 = 1640. And the UPM (Units per Em) of the font is default 1000. Which means in SwiftUI, when .font(.custom(..., size: 1000)) is set, each line of Text() will have a frame whose height is 1640.

Text Box Height

In terms of .lineSpacing(), SwiftUI doesn't set the value for spacing between baselines, but spacing between two boxes instead. If you want to have no spacing between two boxes in the example below, unfortunately setting .lineSpacing() to -(1640-1000) = -640 is not allowed (negative values not acceptable).

Set vs. Expected Line Spacing

UPDATE: An UIViewRepresentable Method

However, you can use UILabel instead to reduce line height:

struct CustomText: UIViewRepresentable {
    let text: String
    let font: UIFont
    
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        
        label.font = font
        label.numberOfLines = 0
        
        let attributedString = NSMutableAttributedString(string: text)
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineHeightMultiple = 0.6  // <- Reduce lineHeight with a <1 factor
        
        attributedString.addAttribute(NSAttributedString.Key.paragraphStyle,
                                      value: paragraphStyle,
                                      range: NSMakeRange(0, attributedString.length))
        
        label.attributedText = attributedString
        
        return label
    }
    
    func updateUIView(_ uiView: UILabel, context: Context) { }
}

Usage:

CustomText(text: "Foggy Days\nGo Nowhere",
           font: UIFont(name: "Catamaran", size: 1000)!)

.leading seems like a thing you need (and is typographically the correct way to talk about 'line spacing').

Use:

Font.system(size: 16, weight: .regular, design: .rounded)
.leading(.tight)
Related