Format text in a variety of ways with this one useful class.
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.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
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)
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.
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
}
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.
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!
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.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.