< How to style text views with fonts, colors, line spacing, and more | How to adjust text alignment using multilineTextAlignment() > |
Updated for Xcode 14.2
SwiftUI’s Text
view is able to render more advanced strings created using Foundation’s AttributedString
struct, including adding underlines, strikethrough, web links, background colors, and more. Sadly, it has a rather bafflingly opaque API so I want to show you a whole bunch of examples to help get you started.
We can create an AttributedString
with common properties such as font, background color, and foreground color:
struct ContentView: View {
var message: AttributedString {
var result = AttributedString("Hello, world!")
result.font = .largeTitle
result.foregroundColor = .white
result.backgroundColor = .red
return result
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
That simple example is something you can do using just Text
and regular SwiftUI modifiers, but part of the power of AttributedString
is that customizations belong to the string rather than to the Text
view used to render it.
This means the background color is part of the string itself, so we can merge several strings together using different background colors if we want:
struct ContentView: View {
var message1: AttributedString {
var result = AttributedString("Hello")
result.font = .largeTitle
result.foregroundColor = .white
result.backgroundColor = .red
return result
}
var message2: AttributedString {
var result = AttributedString("World!")
result.font = .largeTitle
result.foregroundColor = .white
result.backgroundColor = .blue
return result
}
var body: some View {
Text(message1 + message2)
}
}
Download this as an Xcode project
If you try that using Text
and background()
modifiers, you’ll see that it just doesn’t work.
There are a handful attributes we can customize, including underline pattern and color:
struct ContentView: View {
var message: AttributedString {
var result = AttributedString("Testing 1 2 3!")
result.font = .largeTitle
result.foregroundColor = .white
result.backgroundColor = .blue
result.underlineStyle = Text.LineStyle(pattern: .solid, color: .white)
return result
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
You can adjust the baseline offset for pieces of the string, forcing it to be placed higher or lower than default:
struct ContentView: View {
var message: AttributedString {
let string = "The letters go up and down"
var result = AttributedString()
for (index, letter) in string.enumerated() {
var letterString = AttributedString(String(letter))
letterString.baselineOffset = sin(Double(index)) * 5
result += letterString
}
result.font = .largeTitle
return result
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
And we can even attach tappable web links to our text using the link
property:
struct ContentView: View {
var message: AttributedString {
var result = AttributedString("Learn Swift here")
result.font = .largeTitle
result.link = URL(string: "https://www.hackingwithswift.com")
return result
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
However, the really powerful feature of AttributedString
is that it doesn’t throw away all the metadata we provide it about our strings, which unlocks a huge amount of extra functionality.
For example, we can mark part of the string as needing to be spelled out for accessibility reasons, so that things like passwords are read out correctly when using VoiceOver:
struct ContentView: View {
var message: AttributedString {
var password = AttributedString("abCayer-muQai")
password.accessibilitySpeechSpellsOutCharacters = true
return "Your password is: " + password
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
Even more impressive is how it handles structured information.
For example, if we format a Date
instance as an attributed string it retains knowledge of what each component represents – it remembers that “November” is the month part of the string, for example.
This means we can style our strings semantically: we can say “make the whole have a secondary color, apart from the weekday part – that should have a primary color”, like this:
struct ContentView: View {
var message: AttributedString {
var result = Date.now.formatted(.dateTime.weekday(.wide).day().month(.wide).attributed)
result.foregroundColor = .secondary
let weekday = AttributeContainer.dateField(.weekday)
let weekdayStyling = AttributeContainer.foregroundColor(.primary)
result.replaceAttributes(weekday, with: weekdayStyling)
return result
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
Notice how that code has no idea where the weekday actually appears in the text – it’s language and locale independent, so it will be styled correctly for everyone.
The same is true of working with the names of people using PersonNameComponents
– this makes an AttributedString
instance where the family name of someone is bold:
struct ContentView: View {
var message: AttributedString {
var components = PersonNameComponents()
components.givenName = "Taylor"
components.familyName = "Swift"
var result = components.formatted(.name(style: .long).attributed)
let familyNameStyling = AttributeContainer.font(.headline)
let familyName = AttributeContainer.personNameComponent(.familyName)
result.replaceAttributes(familyName, with: familyNameStyling)
return result
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
You can even use it with measurements. For example, the following code creates a measurement of 200 kilometers, then formats that so that the value is presented much larger than the unit:
struct ContentView: View {
var message: AttributedString {
var amount = Measurement(value: 200, unit: UnitLength.kilometers)
var result = amount.formatted(.measurement(width: .wide).attributed)
let distanceStyling = AttributeContainer.font(.title)
let distance = AttributeContainer.measurement(.value)
result.replaceAttributes(distance, with: distanceStyling)
return result
}
var body: some View {
Text(message)
}
}
Download this as an Xcode project
As a bonus, that will automatically honor the user’s locale preference for distance, meaning that many users will see “124 miles” rather than “200 kilometers”.
Warning: If you explore the API using Xcode’s autocomplete, you’ll see all sorts of options that look like they ought to work but in fact do nothing at all.
SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.