let bodyFontDescriptor = UIFontDescriptor
    .preferredFontDescriptor(withTextStyle: UIFontTextStyle.body)
let bodyMonospacedFontDescriptor = bodyFontDescriptor.addingAttributes(
    [
        UIFontDescriptorFeatureSettingsAttribute: [
            [
                UIFontFeatureTypeIdentifierKey: kTextSpacingType,
                UIFontFeatureSelectorIdentifierKey: kMonospacedTextSelector
            ]
        ]
    ])
let bodyMonospacedFont = UIFont(descriptor: bodyMonospacedFontDescriptor, size: 0.0)
textview.font = bodyMonospacedFont
This produces text with characters of variable width. I need to get a monospace font without hardcoding courier new and fixed size. Deployment target is ios 9.0
Here is an extension to UIFontDescriptor that returns a preferred monospaced font descriptor for a given text style. There is no simple way to get a fully monospaced font using UIFont or UIFontDescriptor. This solution attempts to find a good monospaced font and falls back to Courier if needed.
extension UIFontDescriptor {
    static let monoDescriptor: UIFontDescriptor = {
        // Attempt to find a good monospaced, non-bold, non-italic font
        for family in UIFont.familyNames {
            for name in UIFont.fontNames(forFamilyName: family) {
                let f = UIFont(name: name, size: 12)!
                let fd = f.fontDescriptor
                let st = fd.symbolicTraits
                if st.contains(.traitMonoSpace) && !st.contains(.traitBold) && !st.contains(.traitItalic) && !st.contains(.traitExpanded) && !st.contains(.traitCondensed) {
                    return fd
                }
            }
        }
        return UIFontDescriptor(name: "Courier", size: 0) // fallback
    }()
    class func preferredMonoFontDescriptor(withTextStyle style: UIFontTextStyle) -> UIFontDescriptor {
        // Use the following line if you need a fully monospaced font
        let monoDescriptor = UIFontDescriptor.monoDescriptor
        // Use the following two lines if you only need monospaced digits in the font
        //let monoDigitFont = UIFont.monospacedDigitSystemFont(ofSize: 0, weight: .regular)
        //let monoDescriptor = monoDigitFont.fontDescriptor
        // Get the non-monospaced preferred font
        let defaultFontDescriptor = preferredFontDescriptor(withTextStyle: style)
        // Remove any attributes that specify a font family or name and remove the usage
        // This will leave other attributes such as size and weight, etc.
        var fontAttrs = defaultFontDescriptor.fontAttributes
        fontAttrs.removeValue(forKey: .family)
        fontAttrs.removeValue(forKey: .name)
        fontAttrs.removeValue(forKey: .init(rawValue: "NSCTFontUIUsageAttribute"))
        let monospacedFontDescriptor = monoDescriptor.addingAttributes(fontAttrs)
        return monospacedFontDescriptor.withSymbolicTraits(defaultFontDescriptor.symbolicTraits) ?? monospacedFontDescriptor
    }
}
Note the comments about whether you need a font that is fully monospaced or a font that just has monospaced digits. Comment/Uncomment those lines to suit your specific needs.
Sample usage:
let bodyMonospacedFont = UIFont(descriptor: .preferredMonoFontDescriptor(withTextStyle: .body), size: 0)
textview.font = bodyMonospacedFont
The following is some test code to confirm that the results of preferredMonoFontDescriptor(withTextStyle:) works properly for all styles:
let textStyles: [UIFontTextStyle] = [ .body, .callout, .caption1, .caption2, .footnote, .headline, .subheadline, .largeTitle, .title1, .title2, .title3 ]
for style in textStyles {
    let nfont = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: style), size: 0)
    let mfont = UIFont(descriptor: .preferredMonoFontDescriptor(withTextStyle: style), size: 0)
    print(style)
    print(nfont)
    print(mfont)
}
If you compare each pair of results, they have the same size, weight, and style, just a different font.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With