How can I show a fraction with SwiftUI

535 Views Asked by At

I am trying to represent a fraction with denominator larger than 9 in a SwiftUI Text.

I can implement this using individual elements and applying offsets but that get's a bit messy as the fractions change dynamically.

Is there a way to do this using attributedText?

I came across thi UIFont extension with deprecated methods and wondering if anything similar that can be used with SwiftUI:

extension UIFont {
    static func fractionFont(ofSize pointSize: CGFloat) -> UIFont {
        let systemFontDesc = UIFont.systemFont(ofSize: pointSize).fontDescriptor
        let fractionFontDesc = systemFontDesc.addingAttributes(
            [
                UIFontDescriptor.AttributeName.featureSettings: [
                    [
                        UIFontDescriptor.FeatureKey.featureIdentifier: kFractionsType,
                        UIFontDescriptor.FeatureKey.typeIdentifier: kDiagonalFractionsSelector,
                    ], ]
            ] )
        return UIFont(descriptor: fractionFontDesc, size:pointSize)
    }
}
2

There are 2 best solutions below

0
On BEST ANSWER

UIFont is toll-free-bridged with CTFont, which means you can cast a UIFont to a CTFont by saying as CTFont. And SwiftUI's Font has an initializer that takes a CTFont.

So, using the fractionFont(ofSize:) method you posted, this playground code:

PlaygroundPage.current.setLiveView(
    Text("The fraction 21/345 is rendered nicely.")
        .font(Font(UIFont.fractionFont(ofSize: UIFont.systemFontSize) as CTFont))
        .padding()
)

produces this result:

the string "The fraction 21/345 is rendered nicely", with 21 in smaller, raised characters and 345 in smaller, lowered characters

0
On

Building on this, here's a version without the deprecations that takes a UIFont.TextStyle for parameter, allowing you to simply do this:

Text("1/4")
    .font(.fraction(.headline))

Here are the extensions you need:

extension UIFont {
    static func fractionFont(ofSize pointSize: CGFloat) -> UIFont {
        let systemFontDesc = UIFont.systemFont(ofSize: pointSize).fontDescriptor
        let featureSettings: [UIFontDescriptor.FeatureKey: Int] = [
            .type: kFractionsType,
            .selector: kDiagonalFractionsSelector,
        ]
        let attributes = [
            UIFontDescriptor.AttributeName.featureSettings: [
                featureSettings
            ]
        ]
        let fractionFontDesc = systemFontDesc.addingAttributes(attributes)
        return UIFont(descriptor: fractionFontDesc, size: pointSize)
    }
}

extension Font {
    static func fraction(_ style: UIFont.TextStyle) -> Font {
        let preferredFont = UIFont.preferredFont(forTextStyle: style)
        let size = preferredFont.pointSize
        return Font(UIFont.fractionFont(ofSize: size) as CTFont)
    }
}