NSAttributedString: Formatting Rich Text in Swift

Are you looking to add some style to your text in Swift with attributed string? You want to highlight important words, add links, or create beautifully formatted text, then you will want to learn about AttributedString and NSAttributedString. This goes beyonde basic text styling with font in Swift.

NSAttributedString is a class part of the Foundation framework and has been around since the early days of iOS/macOS. It’s Objective-C based and while it’s very powerful, it can be a bit verbose to use. You’ll still find it in many APIs and it’s particularly useful when working with UIKit or AppKit.

AttributedString, is a new struct introduced in iOS 15, is the modern Swift-native implementation. It’s more type-safe and easier to work with in Swift code. It is build to work especially with text views in SwiftUI. However, it is much more limited than NSAttributedString in terms of styling options.

In this guide, I’ll focus primarily on NSAttributedString as it’s still widely used and supported across all iOS/macOS versions. Plus, understanding NSAttributedString will help you better appreciate the improvements made in the newer AttributedString.

Creating and Modifying NSAttributedString

At its core, NSAttributedString is a string that lets you attach attributes (like font, color, or styling) to specific ranges of text. Unlike regular strings that maintain the same formatting throughout, NSAttributedString allows you to create rich text with varying styles within a single string.

Here’s a simple example that creates a string with a red foreground color and bold font:

let attributedString = NSAttributedString(
    string: "Hello World",
    attributes: [
        .foregroundColor: UIColor.red,
        .font: UIFont.boldSystemFont(ofSize: 16)
    ]
)

You pass the base string and a dictionary of attributes. The key value are the attribute names. In the above example, the key is .font and its value is UIFont.boldSystemFont(ofSize: 16). You can add as many attributes as you want.

You can see the rendered attributed string in comparison to the default body text:

NSAttributedString example in Swift

Adding Partial Styling

If you only want to change the styling of a subrange, you can use
NSMutableAttributedString:

let mutableString = NSMutableAttributedString(string: "This is an attributed string with multiple styles.",
                                              attributes: [.font: UIFont.systemFont(ofSize: 17)])

Then you can add attributes to a range of the string. The following makes the foreground color of the words “This is an” blue:

mutableString.addAttribute(
    .foregroundColor,
    value: UIColor.blue,
    range: NSRange(location: 0, length: 11)
)

You can get the range of a string like so:

let range = (mutableString.addAttribute as NSString).range(of: "This is an")

This will give you a range that has a starting location 0 and a length of 11 for the 11 characters.

You can add more addtional attributes for different parts of the string like so:

attributedString.addAttribute(
    .font,
    value: UIFont.boldSystemFont(ofSize: 24),
    range: NSRange(location: 21, length: 21)
)

If you add attributes to ranges that already have a previous value, the newly added attributes will overwrite the old one. For example below code will change the styling of “an” from blue to red:

attributedString.setAttributes([.foregroundColor : UIColor.red],
                               range: NSRange(location: 8, length: 20))

Basic Styling Attributes

How to Change Color of NSAttribuedString

You can change the foreground and background color with the following attributes:

let text = NSMutableAttributedString(string: "Styling Text in Swift")

// Adding color
text.addAttributes([
    .foregroundColor: UIColor.red
], range: NSRange(location: 8, length: 4))

// Adding background color
text.addAttributes([
    .backgroundColor: UIColor.yellow
], range: NSRange(location: 13, length: 5))

How to Change the Font of NSAttribuedString

Here’s how to apply common text attributes, like font size, weight and traits:

let heavyFontPart = NSAttributedString(
    string: "heavy font",
    attributes: [
        .font: UIFont.systemFont(ofSize: 20, weight: .heavy)
    ]
)

let boldfontPart = NSAttributedString(
    string: "bold font",
    attributes: [
        .font: UIFont.boldSystemFont(ofSize: 20)
    ]
)

let italicFontPart = NSAttributedString(
    string: "italic font",
    attributes: [
        .font: UIFont.italicSystemFont(ofSize: 20)
    ]
)

StrikeThrough and Underline Text

You can add attributes for strikethourgh and underline styles, where you can chose between single, double, and thick:

let strikeThroughPart = NSAttributedString(
    string: "strike through text",
    attributes: [
        .strikethroughStyle: NSUnderlineStyle.single.rawValue,
        .strikethroughColor: UIColor.blue
    ]
)

let underlinePart = NSAttributedString(
    string: "underline text",
    attributes: [
        .underlineStyle: NSUnderlineStyle.single.rawValue,
        .underlineColor: UIColor.red,
    ]
)

Adding Clickable Links and Paragraph Styles

You can add a link to a substring with the link attributes. The value should either be a NSURL or String tpye. In the following example, I linked to the Apple website:

let linkText = NSMutableAttributedString(string: "Visit Apple's website")

// Adding a link
let linkRange = NSRange(location: 6, length: 6)
linkText.addAttribute(.link, 
                      value: "https://www.apple.com", 
                      range: linkRange)

Note that the rendered styling is defined by the view where this is used. Even if you set a differnt foreground color, the styling will be internally handled be the view e.g. UITextView/NSTextView.

Text Alignment and Spacing with Paragraph Styles

Styling for text alignment and spacing is handled with paragraph styles. The following creates a styling with center alignmed text and a line spacing of 10:

let style = NSMutableParagraphStyle()
style.alignment = .center
style.lineSpacing = 10

You add this style to the attributes dictionary of NSAttributedString:

You can create customize paragraph formatting:

let firstParagraphAttributes: [NSAttributedString.Key : Any] = [.paragraphStyle: style,
                                                                .font: UIFont.systemFont(ofSize: 17),
                                                                .foregroundColor: UIColor.blue]

let firstParagraphText = """
First Paragraph
This paragraph demonstrates center alignment with increased line spacing.
"""

let firstParagraph = NSAttributedString(string: firstParagraphText,
                                       attributes: firstParagraphAttributes)

Other parameters that you can change per paragraph are:

style.firstLineHeadIndent = 40  // Indent first line
style.headIndent = 20           // Indent other lines

style.minimumLineHeight = 25
style.maximumLineHeight = 30

style.lineSpacing = 10
style.paragraphSpacing = 20     // Space after paragraph
style.paragraphSpacingBefore = 10  // Space before paragraph

You can also work with paragraph styles if you want to create lists:

let paragraphStyle = NSMutableParagraphStyle()
let textList = NSTextList(markerFormat: .disc, options: 1) // Bullet format
paragraphStyle.textLists = [textList]

Adding Images and Attachments

Here’s something cool – you can even embed images in your attributed strings! This is perfect for creating rich content:

let attachment = NSTextAttachment()
attachment.image = UIImage(systemName: "star.fill")
attachment.bounds = CGRect(x: 0, y: -5, width: 20, height: 20)

let imageString = NSAttributedString(attachment: attachment)
let textString = NSAttributedString(string: " Rating: 4.5/5")

let combination = NSMutableAttributedString()
combination.append(imageString)
combination.append(textString)

return combination

Pro tip: When working with text attachments, pay attention to the bounds property. The y value can be used to adjust the vertical alignment of your image relative to the text baseline.

Using NSAttributedString with SwiftUI and UIKit

When it comes to implementing NSAttributedString in your iOS apps, you have different approaches depending on whether you’re using UIKit or SwiftUI. Let me show you how to handle both frameworks effectively.

Using NSAttributedString with UIKit

In UIKit, several UI components directly support NSAttributedString through their attributedText property. Here’s how you can use it with UILabel:

// Using with UILabel
let label = UILabel()
let attributedText = NSAttributedString(
    string: "Hello World",
    attributes: [
        .foregroundColor: UIColor.blue,
        .font: UIFont.boldSystemFont(ofSize: 20)
    ]
)
label.attributedText = attributedText

You can also set an attributed string to UITextView

// Using with UITextView
let textView = UITextView() // wil use Textkit 2 per defaul
textView.attributedText = attributedText

The actual rendering is done by the view which might ignore certain attributes. It might also be different for TextKit 1 or 2 and if you are on macOS or iOS.

Attributed String in SwiftUI

SwiftUI introduced its own AttributedString type in iOS 15, which is more Swift-friendly than NSAttributedString. Here is a simple example in SwiftUI:

struct ContentView: View {
    var body: some View {
        Text(makeAttributedString())
    }

    func makeAttributedString() -> AttributedString {
        var attributedString = AttributedString("Hello World")
        attributedString.foregroundColor = .blue
        attributedString.font = .system(.title, design: .rounded)

        if let range = attributedString.range(of: "World") {
             attributedString[range].backgroundColor = .green
        }
        return attributedString
    }
}

AttributedString is very limited. For example header styles are not supported. and only supports the following stylings:

  • font (including bold and italic)
  • foregroundcolor
  • backgroundColor
  • strikethroughStyle
  • underlineStyle

You can also use markdown with AttributedString. But the markdown support is very basic and limited to the following:

let thankYouString = try AttributedString(markdown:"This is a **bold** and *italic* and `code` with ~~strikethrough~~. [click this link](https://www.apple.com)")

Converting Between AttributedString and NSAttributedString

Sometimes you need to convert between SwiftUI’s AttributedString and NSAttributedString:

// Converting NSAttributedString to AttributedString
NSAttributedString(attributedString: attributedString)

Note that SwiftUI does not render all attributes. For example, NSList or NSTable will not be shown.

Getting Attributes from NSAttributedString

When working with NSAttributedString, you’ll often need to read and analyze the attributes applied to different parts of the text. Let me show you how to effectively retrieve these attributes in your Swift code.

I will use the following as an example:

let attributedString = NSMutableAttributedString(string: "A text with a background color.")

// Applying foreground color to "text"
attributedString.addAttributes(
    [.foregroundColor : UIColor.red],
    range: NSRange(location: 2, length: 4)
)

// Applying background color to "text with a background"
attributedString.addAttributes(
    [.backgroundColor: UIColor.yellow],
    range: NSRange(location: 2, length: 22)
)

Basic Attribute Retrieval

The simplest way to get attributes is using the attributes(at:effectiveRange:) method:

// Get attributes at a specific location
if let attributes = attributedString.attributes(at: 3, effectiveRange: nil) {
    if let color = attributes[.foregroundColor] as? UIColor {
        print("Text color at position 3 is: (color)")
    }
}

Enumerating Single Attributes

If you’re interested in finding all occurrences of a specific attribute (like background color), you can use enumerateAttribute:

let fullRange = NSRange(location: 0, length: attributedString.length)

  //Enumerate .backgroundColor to detect highlighted text.
attributedString.enumerateAttribute(.backgroundColor, in: fullRange, options: []) { value, range, _ in
    if let color = value as? NSColor {
       let text = attributedString.attributedSubstring(from: range)
       print("found a background color for text: (text)")
    }
}

The closure will be called 3 times, where the second time the range for the yellow background color is given.

Enumerating All Attributes

To examine all attributes in a string, use enumerateAttributes:

let fullRange = NSRange(location: 0, length: attributedString.length)

attributedString.enumerateAttributes(in: fullRange) { attributes, range, _ in
    // Check for background color
    if let backgroundColor = attributes[.backgroundColor] as? UIColor {
        print("Range (range) has background color: (backgroundColor)")
    }

    // Check for text color
    if let textColor = attributes[.foregroundColor] as? UIColor {
        print("Range (range) has text color: (textColor)")
    }

    // Check for font
    if let font = attributes[.font] as? UIFont {
        print("Range (range) uses font: (font)")
    }
}

The closure will be called for all range that have differnt attributes. For the above attributes you will get 4 ranges:

Performance Tips 💡

  • Cache attributed strings when possible
  • Use NSMutableAttributedString only when you need to modify the text
  • Consider using AttributedString for SwiftUI-only apps in iOS 15+

Conclusion

NSAttributedString is an essential tool for any iOS developer looking to create sophisticated text experiences in their apps. By mastering its capabilities, you can create more engaging and visually appealing applications that stand out in the App Store. Start implementing these concepts in your next project, and remember to consider both UIKit and SwiftUI approaches for maximum flexibility and compatibility.

Further Reading:

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!

Newsletter Form