Using Font Features in SwiftUI

Iam trying to use the SF Text OpenType features in a swiftui app. But have no starting point and couldnt find any documentation about font features. Maybe you can help my out with that.

— beginner iOS dev here

You mean to set Font? You use font(_:) modifier.

In web its called font-feature-settings. In Sketch its under Text > OpenType Features. There are options to enable certain font features of SF Pro like alternative style sets and ligatures.
The font modifier cant do this, does he?

SwiftUI’s Font doesn’t have that setting. In any case, if you want fine-grained control, you may need to drop down into CTFont, but, I do not know if CTFont would even have it. Though AFAICT, things like ligatures are enabled by default.

It doesn't seem like font features are available in SwiftUI, but they’re definitely available in CoreText, and it seems like you can create a SwiftUI Font from a CTFont.

As an example, the following should give you the system font with small caps enabled:

let font = CTFontCreateUIFontForLanguage(.label, 24, nil)!

let fontFeatureSettings: [CFDictionary] = [
    [
        kCTFontFeatureTypeIdentifierKey:     kLowerCaseType,
        kCTFontFeatureSelectorIdentifierKey: kLowerCaseSmallCapsSelector
    ] as CFDictionary
]

let fontDescriptor = CTFontDescriptorCreateWithAttributes([
    kCTFontFeatureSettingsAttribute: fontFeatureSettings
] as CFDictionary)

let fontWithFeatures = CTFontCreateCopyWithAttributes(font, 24, nil, fontDescriptor)

let text = Text("Hello").font(Font(fontWithFeatures))

You can find a list of the different constants here.

3 Likes

Thank you very much! Thats what I need.

1 Like

Is there anyway to set background color on Text? While keeping Text() as Text, and not turn into some View (so can be concatenated '+' together)? .background() is no good because it returns a View, not Text :frowning:

Edit: foregroundColor(), bold(), italic(), underline() etc return Text() which let you do "attributed string" like thing with '+". Just wish .background() also returns Text, not View.

I’m not very well-versed in SwiftUI, so I don’t know. Someone else might!

That's not generally how SwiftUI works. Views define their base properties and then are composed together using modifiers. If you want to work with attributed strings, you may be better off wrapping UILabel.

Hopefully SwiftUI2 will bring better text support.

How does the attributedString approach work with swiftUi? Can you give me a example?

Pretty sure Text is SwiftUI's (attempted) attributed string. It can concatenates texts with different font styles together. Otherwise, as @Jon_Shier said, bridge UILabel over instead.

I'm pretty sure that's not true, as attempting to use Text views placed together breaks many text attributes, and changes to other layout can affect the string's layout. I wish for real HTML and Markdown rendering, as well as direct support for NSAttributedString, or an equivalent.

Well yeah, there's not a lot going on for Text. But the API structure (adjusting font/color results in Text, + operator) doesn't seem to serve other purpose (other than attributing parts of the text). So at the very least, I'd say it's an attempted attributed string.

Just so that we're on the same page, you're referring to this, right?:

Text("test").italic() + Text(" this").bold() + Text(" text").foregroundColor(.blue)

That said, agreed that it could be more fully-featured.

Well, really you'd need to use an HStack, but sure. But building a single text run that way breaks any interword spacing as well as any specific beginning / middle / end formatting, plus other issues. I can't imagine it was actually intended to serve the same role as NSAttributedString.

I don't quite followed (in part because I didn't use a lot of NSAttributedString). Isn't it the same way as NSAttributedString, that you still build each strings part-by-part, and keep appending them to form a complete text? If anything what's really missing would be missing attributes + ability to directly parse markdowns like you said.

There's also ability to set attributes to a range of existing string, which I guess it could be more ergonomics. Though I don't see how it'd work with SwiftUI.

Concatenated NSAttributedStrings are the same thing as a single NSAttributedString with styled ranges, so you get the same features either way. So it's one string with full styling, including paragraphs. Using Text views together is literally just using separate views, so you get no more styling than the size of your Text elements, and no higher level styling like paragraphs.

Text properly does word wrapping too, so I'd say they're more intimated than being separated views.

Anyhow I agree that Text does lack a lot of feature to be attributed-ready. The multilineTextAlignment(_:) doesn't even have full-justified (mayhaps that's why it's not called textJustification). Though I still think the API is still heading in that direction, in that someone could just add "a few" more and have it work harmoniously with the existing API, if not enhanced by it. I think we could agree to disagree.

VStack does special case default spacing between Text's:

            VStack {        // default spacing
                Color.red
                    .frame(width: 55, height: 15)
// spacing here is some size not sure what
                Color.red
                    .frame(width: 55, height: 15)
// spacing between here is larger
                Text("Hello ") +
                Text("my name ") +
                Text("is Paul")
// in between Text, spacing is 0
                Text("I live at the end of the rainbow!")
// same here in between Text spacing is 0
                HStack {
                    Text("Lorem")
                    Text("Ipsom")
                }
// even in between Text and HStack, when everything is Text, spacing is 0!
                Text("in between ultra magenta and cyan")
            }

VStack seems to deliberately treat spacing between Text different from anything else. As soon as you mix Text something else, default spacing is different.