4

I need precise control over the area taken by Text. I created the most basic program that shows unexpected top and bottom spacing being added to a text. Where is this extra padding coming from? How to get rid of it?

@main
struct myApp: App {
    
    
    init() {    }
    
    var body: some Scene {
        WindowGroup {
            Text("80")
                .font(.system(size: 30, weight: .bold, design: .default))
                .background(Color.red)
        }
    }
}

enter image description here

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
HixField
  • 3,538
  • 1
  • 28
  • 54

3 Answers3

5

Text seems to have default padding as seen here

enter image description here

You can get past that by adjusting the padding to a negative amount

Replace your code with this

Text("80")
        .font(.system(size: 30, weight: .bold, design: .default))
        .padding(.vertical, -6)
        .background(Color.red)

Here is a solution if you want to make it dynamic

  struct TestView: View {
    var fontSize: CGFloat = 110
    var paddingSize: CGFloat {
        -(fontSize * 0.23)
    }
    var body: some View {
        Text("80")
            .font(.system(size: fontSize, weight: .bold, design: .default))
            .padding(.vertical, paddingSize)
            .background(Color.red)
        Text("Hello")
    }
}

So even with a large font size of 110 like in the picture you can render it how you like enter image description here

You'll need to tweak the multiplier how you see fit

Sergio Bost
  • 2,591
  • 2
  • 11
  • 29
  • Yes but the amount of padding seams not to be constant and depends on font size I think... – HixField Mar 12 '21 at 16:14
  • @HixField check the edit, that way it will be dynamic, create a computed property that will read whatever your font size – Sergio Bost Mar 12 '21 at 16:21
  • @HixField If you want to eliminate horizontal padding then `.padding(.horizontal, x)` – Sergio Bost Mar 12 '21 at 16:26
  • 2
    It’s a shame we have to resort to these magic numbers like 0.23. I am looking into string size for a more generic approach. Will post answer when I get it working – HixField Mar 12 '21 at 18:18
2

Turns out that the padding top and bottom is actually not there at all. In my example with numbers this looks like wasted space, but actually certain characters do need this room as can be seen on this picture: enter image description here

Since I am only going to show numbers I have used the solution by @Sergio to correct for this overspace.

HixField
  • 3,538
  • 1
  • 28
  • 54
0

For everyone still struggling with this, let me add my approach to the list. I know it's not wasted space considering characters like Ö and g, but when designing with a grid, you usually want to use the cap height of a font.

My approach is still kinda hacky, but I didn't want to use magic numbers. The trick is, using the boundingRect String function, you can get the exact height of a single line of text including the padding:

let uiFont = UIFont.systemFont(ofSize: 40, weight: .semibold)

let heightOfOneLine = "X".boundingRect(
    with: CGSize(width: CGFloat(10), height: .greatestFiniteMagnitude),
    options: .usesLineFragmentOrigin,
    attributes: [.font: uiFont],
    context: nil
).size.height

You can use this to then compute the vertical padding based on the cap height of the font:

let padding = (heightOfOneLine - uiFont.capHeight) / 2

This is the vertical padding that you need to subtract to get an area with exactly the cap height:

.padding(.vertical, -padding)

Make sure to either use the UIFont for your text by converting it to a Font or to create a Font with the same properties. It's unfortunate that there is still no way to get the UIFont out of a Font and that you can only use a UIFont for these computations.

You can test all this, using the following View:

struct TestView: View {
    private let uiFont = UIFont.systemFont(ofSize: 200, weight: .semibold)

    var body: some View {
        Text("X")
            .font(Font(uiFont))
            .padding(.vertical, -getPadding())
            .background(.red)
    }

    private func getPadding() -> CGFloat {
        let heightOfOneLine = "X".boundingRect(
            with: CGSize(width: CGFloat(10), height: .greatestFiniteMagnitude),
            options: .usesLineFragmentOrigin,
            attributes: [.font: uiFont],
            context: nil
        ).size.height

        return (heightOfOneLine - uiFont.capHeight) / 2
    }
}
hoelska
  • 39
  • 6