UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

NSAttributedString by example

Format text in a variety of ways with this one useful class.

Paul Hudson       @twostraws

Swift's strings are great for storing plain text, but as soon as you want formatting, images, or interactivity you need to reach for NSAttributedString - Foundation’s all-in-one string handling class. These are used in various places in iOS and macOS, but you're most likely to want to use them with UILabel and UITextView, both of which accept attributed strings directly.

In this article I'll walk you through examples of what NSAttributedString is capable of: creating strings by hand, adding and enumerating attributes, adding links and images, and more. All these code samples are written to work with a Swift playground, which is a particularly good place to try out attributed strings because Xcode can render a preview of how the string looks.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Creating attributed strings

Let’s start with a trivial attributed string: one that holds a regular string with no special styling. Try this in a playground:

let quote = "Haters gonna hate"
let attributedQuote = NSAttributedString(string: quote)

When that runs, click the preview button for attributedQuote and you should be able to preview it. It doesn’t look special – in fact, it looks just like a regular string – but that’s because we haven’t applied any styling yet.

Try this instead:

let quote = "Haters gonna hate"
let font = UIFont.systemFont(ofSize: 72)
let attributes = [NSAttributedString.Key.font: font]
let attributedQuote = NSAttributedString(string: quote, attributes: attributes)

Now we’re asking for the same text to be rendered in a 72-point font, so your preview should be much larger.

What you put in the attributes is down to how you want the string to look. For example, this will make the text red:

let font = UIFont.systemFont(ofSize: 72)
let attributes: [NSAttributedString.Key: Any] = [
    .font: font,
    .foregroundColor: UIColor.red,
]

Alternatively, this will color the text white and give it a red glow:

let font = UIFont.systemFont(ofSize: 72)
let shadow = NSShadow()
shadow.shadowColor = UIColor.red
shadow.shadowBlurRadius = 5

let attributes: [NSAttributedString.Key: Any] = [
    .font: font,
    .foregroundColor: UIColor.white,
    .shadow: shadow
]

If you want to adjust paragraph-level settings - text alignment, indents, line spacing, and so on – this is done using a separate type called NSMutableParagraphStyle. For example, this creates and applies a paragraph style to make our text centered and have the first line in every paragraph indented by five points:

let font = UIFont.systemFont(ofSize: 72)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.firstLineHeadIndent = 5.0

let attributes: [NSAttributedString.Key: Any] = [
    .font: font,
    .foregroundColor: UIColor.blue,
    .paragraphStyle: paragraphStyle
]

let attributedQuote = NSAttributedString(string: quote, attributes: attributes)

Modifying existing attributed strings

All the above is about creating an attribute string from scratch, but often you’ll want to take an existing attributed string and modify it. For example, if you have some text and the user wants to make part of it bold – rather than create the whole thing from scratch you’ll want to modify what you have.

Let’s start with our basic string again:

let quote = "Haters gonna hate"
let attributedQuote = NSMutableAttributedString(string: quote)

Notice that uses an NSMutableAttributedString now – it’s an attributed string we can modify. Thanks to the way classes work, we can modify this object even though it’s declared as a constant using let.

If we wanted to make the word “gonna” red, we’d call the addAttribute() method of that attributed string, like this:

attributedQuote.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: 7, length: 5))

Yes, that uses NSRange to specify string ranges, which is unfortunate but unavoidable.

If you want to add multiple attributes at the same time, you should use addAttributes instead – it uses the same dictionary approach we used earlier. For example, this will apply a green background color and 10 points of kerning (letter spacing) to the word “Haters”:

let attributes: [NSAttributedString.Key: Any] = [.backgroundColor: UIColor.green, NSAttributedString.Key.kern: 10]
attributedQuote.addAttributes(attributes, range: NSRange(location: 0, length: 6))

While this approach isn’t hard to use – and it’s certainly efficient – I nearly always find it easier to create multiple independent attributed strings then join them together. This approach is both a little slower and a little more memory-intensive, but I find it easier to grasp when working with complex string layouts.

So, the complete code would look like this:

let quote = "Haters gonna hate"

let firstAttributes: [NSAttributedString.Key: Any] = [.backgroundColor: UIColor.green, NSAttributedString.Key.kern: 10]
let secondAttributes = [NSAttributedString.Key.foregroundColor: UIColor.red]

let firstString = NSMutableAttributedString(string: "Haters ", attributes: firstAttributes)
let secondString = NSAttributedString(string: "gonna ", attributes: secondAttributes)
let thirdString = NSAttributedString(string: "hate")

firstString.append(secondString)
firstString.append(thirdString)

This approach segments work into simpler chunks, which means I can in turn compose new attributed strings by mixing and matching various function calls.

Loading an attributed string from HTML

An alternative way to create attributed strings is to have iOS parse some HTML. Although only a subset of styles are supported, it should be enough to handle fonts, sizes, and colors just fine.

For example, we could define some HTML using inline CSS to make a paragraph blue:

let html = """
<html>
<body>
<p style="color: blue;">This is blue!</p>
</body>
</html>
"""

We could then convert that to an instance of Data so it can be read into an attributed string:

let data = Data(html.utf8)

Finally, we can load that data into an NSAttributedString, telling it that we’re using HTML for the document type:

if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
    // use your attributed string somehow
}

Enumerating attributes

If you have an attributed string someone else made – for example reading page text from a PDFView – you can loop over all the attributes to figure out where they are and what they contain.

For example, here’s some code that creates the string “the cat sat on the mat”, where the first, third, and fifth words are larger than the others:

let sentence = "the cat sat on the mat"
let regularAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12)]
let largeAttributes = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 24)]
let attributedSentence = NSMutableAttributedString(string: sentence, attributes: regularAttributes)

attributedSentence.setAttributes(largeAttributes, range: NSRange(location: 0, length: 3))
attributedSentence.setAttributes(largeAttributes, range: NSRange(location: 8, length: 3))
attributedSentence.setAttributes(largeAttributes, range: NSRange(location: 15, length: 3))

We could enumerate over the font attribute for that string, check to see whether the font is bold, and if it is also color that section red. This is all done using the enumerateAttribute() method: it will find all instances of the attribute you requested, passing them to you one by one along with their range and a stop variable. If you set the stop variable to true, the loop ends.

Here’s that in code:

attributedSentence.enumerateAttribute(.font, in: NSRange(0..<attributedSentence.length)) { value, range, stop in
    if let font = value as? UIFont {
        // make sure this font is actually bold
        if font.fontDescriptor.symbolicTraits.contains(.traitBold) {
            // it's bold, so make it red too
            attributedSentence.addAttribute(.foregroundColor, value: UIColor.red, range: range)
        }
    }
}

There’s also a more general enumerateAttributes() method that gives you all attributes in a specific range.

Placing an image inside an attributed string

So far you might be thinking that attributed strings seem relatively easy, but let’s mix things up a little by adding images right inside a string. This is best done inside an app project so that you can load image files more easily.

First, create a regular mutable attributed string with some content:

let fullString = NSMutableAttributedString(string: "Your rank: ")

Second, create an instance of NSTextAttachment – a class specifically designed to let us attach images to attributed strings”

let image1Attachment = NSTextAttachment()
image1Attachment.image = UIImage(named: "rank-major.png")

Third, we can wrap that attachment inside its own attributed string so we can append it to the previous string:

let image1String = NSAttributedString(attachment: image1Attachment)

Finally, we can add the NSTextAttachment wrapper to our full string:

fullString.append(image1String)

That’s it – an image inside our attributed string!

Placing a hyperlink inside an attributed string

Web links are just like any other attributed string attribute, meaning that you can add NSAttributedString.Key.link to any range of your string, along with a URL that should be shown when the link is clicked.

For example:

let attributedString = NSMutableAttributedString(string: "Want to learn iOS? You should visit the best source of free iOS tutorials!")
attributedString.addAttribute(.link, value: "https://www.hackingwithswift.com", range: NSRange(location: 19, length: 55))

Note: this just creates the link, it doesn’t add styling for it. Links will appear blue and underlined by default, but you can set the linkTextAttributes attributed of your text view to whatever attributes you want.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.