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.

4 (1) · 1 Review

Download materials
Save for later
Share

Building great-looking apps doesn’t rely on just images — it also extends to text. Different styles in attributed strings can go a great distance to making information more appealing. In this tutorial, you’ll learn about the new AttributedString value type introduced in iOS 15 and macOS 12. You’ll also see how to leverage its capabilities, including formatting with Markdown, to do more with text in your apps.

This tutorial will cover:

  • Differences between the new AttributedString and the older NSAttributedString that’s bridged from Objective-C.
  • Formatting and styling an attributed string using Markdown.
  • The structure of an attributed string and how to alter it.
  • Creating and rendering custom attributes.
  • Encoding and decoding an attributed string and its custom attributes.
Note: This tutorial assumes some familiarity with iOS programming, which often includes working with strings. If you’re brand new, consider starting with a book like SwiftUI Apprentice or a video course like Your First iOS and SwiftUI App: An App From Scratch. Feel free to give this tutorial a go though and jump into the forum (linked below) to ask questions!

Getting Started

Download the starter project by clicking Download Materials at the top or bottom of the tutorial.

The app you’ll build, Markdown Preview, allows you to type a basic text string that it then converts to an attributed string. Then, it saves this attributed string to a library of your creation.

Start by opening MarkdownPreview.xcodeproj in the starter folder. Build and run the app to see your starting point.

Markdown Preview app start screen

The first section of the screen allows you to choose a theme. A group of themes is already included in the project but won’t have any effect yet.

Markdown Preview app Select Theme screen with four options

You’ll divide the work on this app into five parts:

  1. Converting a Markdown string to an attributed string.
  2. Applying the themes on the text without permanently changing its attributes.
  3. Creating custom attributes that can be part of your Markdown.
  4. Creating a text view that can render the new custom attributes.
  5. Saving your attributed string into a library.

AttributedString vs. NSAttributedString

Before you start working on the project, it’s worth knowing a few things about AttributedString in comparison with the older NSAttributedString. Specifically, it:

  • Is a first-class Swift citizen and takes advantage of Swift features, similar to the differences between String and NSString.
  • Is a value type, while the older NSAttributedString is a reference type.
  • Conforms to Codable. You can directly encode and decode an AttributedString object along with its attributes just like working with a normal String.
  • Has the same character-counting behavior as String.
  • Is fully localizable. You can even define styles in your text directly in the localization files!
  • Most importantly, AttributedString has full support for Markdown.

Using Markdown

Markdown is a popular markup language for formatting text. It can format whole documents — not just paragraphs. You might be surprised to learn that all the books published here on raywenderlich.com are written entirely in Markdown. :]

Note: You can learn more about Markdown’s syntax from the Markdown Cheat Sheet.

Write a Markdown string in the Raw Markdown text field, and notice that the text appears as-is in the Rendered Markdown area. The Markdown attributes aren’t translated to style the text yet.

Markdown attributes not translated to style the text.

Open MarkdownView.swift in the Views group, then go to convertMarkdown(_:). This method handles converting your raw text to an AttributedString. Your text isn’t treated as Markdown on its own if you use the standard initializer AttributedString(_:). Change the implementation of the method to:

// 1
guard var attributedString = try? AttributedString(markdown: string) else {
  // 2
  return AttributedString(string)
}
// 3
printStringInfo(attributedString)
// 4
return attributedString

The code you added does the following:

  1. Tries to convert the raw string to an attributed string using the initializer AttributedString(markdown:).
  2. If it fails, then it creates an attributed string using the default initializer without any Markdown styling.
  3. Prints some information about the attributed string. This method is currently empty. You’ll implement it in the next section.
  4. Returns the attributed string that succeeded in the Markdown initializer.

Build and run. Enter the same Markdown string you tried before in the Raw Markdown area, and see how it appears now:

Raw Markdown with correct styling.

Examining the Structure of an AttributedString

An AttributedString object consists of characters and you can count them just like a normal string. But it also consists of AttributedString.Runs.

Runs are the parts of an AttributedString that describe what style applies to which characters in the text. Each run consists of a range for a substring and the styles applied to it. If your text is plain and has no styles, then your attributed string will consist of only one run. If your AttributedString uses a variety of styles, then it’ll be broken down into many runs. You’ll get deeper into runs shortly.

Characters and Indices

To get a better idea of what characters are, return to Views/MarkdownView.swift, then go to printStringInfo(_:). Implement it as follows:

// 1
print("The string has \(attributedString.characters.count) characters")
// 2
let characters = attributedString.characters
// 3
for char in characters {
  print(char)
}

Here’s what’s happening in the code above:

  1. Print the number of characters in the attributed string.
  2. Create a variable holding the AttributedString.CharacterView that you’ll iterate over to get the value of each character separately.
  3. Iterate over this collection and prints the value of the characters one by one.

Build and run. Enter this raw Markdown string to try it out:

This is **Bold text** and this is _italic_

You’ll see the output from printStringInfo(_:) in Xcode’s console:

The string has 36 characters

The original string is 42 letters. But when treated as Markdown, the ** and _ characters that are part of the Markdown syntax are no longer part of the actual string. They became attributes or style, not content.

Scroll up a little in the log, and you’ll find that the previous log on the character count is 37. That happened right before you entered the last _, when the string was:

This is **Bold text** and this is _italic

You hadn’t entered the closing _ for the italic syntax, so AttributedString wasn’t treating this part as italic yet. Both opening and closing characters must be present, otherwise they’re considered part of the content.

Attributed string with a missing closing character for italic style

Notice that the italic style isn’t applied, and the _ is part of the content in the attributed string.