What is AttributedString, and how is it different from NSAttributedString?

2.8.1 · L2 · Interview

Predict first

How many runs does this have?

var s = AttributedString("Hello world")
s[s.range(of: "world")!].link = URL(string: "https://x.com")
print(s.runs.count)
  • 1
  • 2
  • 3

Predict first

What does this print?

let s = try! AttributedString(markdown: "**bold** text")
print(s.runs.count, String(s.characters))
  • 1 **bold** text
  • 2 bold text
  • 2 **bold** text

Mental model. An attributed string is plain text with highlighter marks. A run is one stretch drawn in the same colour.

AttributedString (iOS 15) is the Swift-native styled text: a value type that holds characters plus attributes stored in runs (stretches that share the same attributes). It is Hashable, Codable and Sendable. You style a part by subscripting with a range: s[range].link = url. It can also parse inline Markdown with AttributedString(markdown:).

NSAttributedString is the older Objective-C class (with a mutable subclass), which UIKit and TextKit still use. Attributes belong to scopes: the Foundation scope has link, while font and foregroundColor live in the UIKit, AppKit and SwiftUI scopes, so you pick the scope when you read or write them.

The link splits the text into two stretches with different attributes: "Hello " and "world".

The Markdown markers are consumed and become an attribute on the first run, so the characters are plain bold text.

Common traps

  • Looking for font without a scope. It exists only in the UIKit, AppKit or SwiftUI scope.
  • Assuming a mutation affects the original. It is a value type, so copies are independent.

They might follow up with

  • How do you iterate styling? for run in s.runs { run.range; run.link }.
  • What does Markdown parsing give you? Attributes such as inlinePresentationIntent for bold and italic, which you map to fonts yourself.

Related questions

  • 2.8.2 Why do ranges break when you move between String, AttributedString and NSAttributedString?
  • 1.9.2 Character vs Unicode.Scalar vs UTF-8 code unit?

Why do ranges break when you move between String, AttributedString… →