AttributedString Tutorial for Swift: Getting Started
Learn how to format text and create custom styles using iOS 15’s new AttributedString value type as you build a Markdown previewer in SwiftUI. By Ehab Amer.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
AttributedString Tutorial for Swift: Getting Started
30 mins
- Getting Started
- AttributedString vs. NSAttributedString
- Using Markdown
- Examining the Structure of an AttributedString
- Characters and Indices
- Runs
- Applying the Themes
- Defining Theme Styles
- Creating Custom Attributes
- Attribute Scopes
- Rendering Custom Attributes
- Saving Styled Strings
- Saving Custom Attributes
- Saving Fonts
- Where to Go From Here?
Saving Custom Attributes
When you saved your string, the formatting showed in the list — but when you relaunch the app, its styling is lost! There’s a simple reason for this. When you saved the string the first time, it was directly added to the data source array. But when you relaunched the app, the data source reloaded everything from a file. The attributed string with all its custom attributes was saved correctly in the file, but the custom attributes aren’t there the second time you launch the app.
The decoder in the data source doesn’t know anything about the attributes scope you created to encode and decode your custom attributes. And in a way, it shouldn’t care about that since it’s a generic decoder.
Saving Fonts
There’s also another issue. If you created some strings with a theme, you’ll notice that the font is also lost in the decoding process. This time, it’s not something you missed. It seems that SwiftUI.Font
doesn’t conform to Codable
, so its value isn’t stored.
To fix the first issue, you’ll need to wrap an attributed string in another type and configure the attributed string property with a Codable
configuration to consider the new scope. And for the second, you’ll just save the selected theme alongside the attributed string and reapply the theme when you add it to the list.
Create a new Swift file in the Models group named CustomAttributedString.swift. Add the following to it:
import SwiftUI
struct CustomAttributedString: Codable, Identifiable, Hashable {
// 1
func hash(into hasher: inout Hasher) {
hasher.combine(textTheme)
hasher.combine(attributedString)
}
// 2
var id: Int {
attributedString.hashValue
}
// 3
var textTheme: TextTheme
// 4
@CodableConfiguration(from: \.customAttributes) var attributedString =
AttributedString()
// 5
init(_ attString: AttributedString, theme: TextTheme) {
attributedString = attString
textTheme = theme
}
// 6
var themedString: AttributedString {
var tempString = attributedString
tempString.mergeAttributes(textTheme.attributeContainer)
return tempString
}
}
Here’s what this does:
- Uses the hashing function to generate the hash of the current object by merging the hash values of its two stored properties.
- Defines a property,
id
, that returns the hash value.Identifiable
requires this property. - Sets a property for the theme of this string to reapply it whenever you need the string.
- Attaches a
CodableConfiguration
with theAttributeScopes.CustomAttributes
type that you created to handle decoding the custom attributes. - Adds an initializer for the new type.
- Sets a computed property for the attributed string that reapplies the theme.
Next, you’ll need to apply some changes to accommodate for the new type. In MarkdownView.swift, change the type of dataSource
to:
var dataSource: AttributedStringsDataSource<CustomAttributedString>
Then, change the implementation of saveEntry()
to:
func saveEntry() {
let originalAttributedString = convertMarkdown(markdownString)
let customAttributedString = CustomAttributedString(
originalAttributedString,
theme: selectedTheme)
dataSource.save(customAttributedString)
cancelEntry()
}
This saves the new type that includes the original attributed string along with the theme instead of the attributed string alone.
Next, open SavedStringsView.swift and change the type of dataSource
to:
@ObservedObject var dataSource:
AttributedStringsDataSource<CustomAttributedString>
Finally, in the body, change the loop that adds the strings in the list to:
ForEach(dataSource.currentEntries, id: \.id) { item in
CustomText(item.themedString)
.padding()
}
This will use the new type in the listing page and recreate the theme attributes in the attributed string for presentation.
Uninstall the app from the simulator by long-tapping the app icon. Then, tap Remove app to delete the previously saved list.
Build and run. Save some strings with different themes, then restart the app.
You’ll see that all the styles are correctly applied in the list.
Where to Go From Here?
You can download the completed project files by clicking Download Materials at the top or bottom of the tutorial.
To learn more about AttributedString
, have a look at the developer documentation.
You can also check out Apple’s session from WWDC21 that introduces AttributedString
. The session references a sample project that illustrates overlapping attributes, localization and even rainbow text!
You should also check out SwiftUI Localization Tutorial for iOS: Getting Started to learn more about localization, pluralizations and grammar.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!