skip to Main Content

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

Answers


  1. 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

    Login or Signup to reply.
  2. 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)
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search