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
}
}