Search code examples
iostextfontsswiftuiline-spacing

Fix line spacing in custom font in SwiftUI


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


Solution

  • 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)!)