How to add advanced text styling using AttributedString in swiftUI?

By | August 5, 2024

In our previous post we talked about styling swiftUI text views with fonts, colors, line spacing and more. This time we will look deep into ‘AttributedString’. SwiftUI’s ‘Text’ view is able to render more advanced strings created using ‘AttributedString’ struct. This includes adding underlines, strike through, web links, background colours etc. We will show you a bunch of examples of the same.

We can create an ‘AttributedString’ with common properties such as font, background color, and foreground color.

struct ContentView: View {
    var message: AttributedString {
        var result = AttributedString("Hello, world!")
        result.font = .largeTitle
        result.foregroundColor = .white
        result.backgroundColor = .red
        return result
    }

    var body: some View {
        Text(message)
    }
}

The above example is something you can do with just ‘Text’ and regular text modifiers. But when we use ‘AttributedString’, the customization belongs to the string, rather than the ‘Text’ view used to render it. This means the background color is part of the string itself. So we can merge several strings together using different background colors if we want.

    var message1: AttributedString {
        var result = AttributedString("Good")
        result.font = .largeTitle
        result.foregroundColor = .white
        result.backgroundColor = .red
        return result
    }

    var message2: AttributedString {
        var result = AttributedString("Morning!")
        result.font = .largeTitle
        result.foregroundColor = .white
        result.backgroundColor = .blue
        return result
    }

    var body: some View {
        Text(message1 + message2)
    }

There are handful of attributes that we can customize, including underline pattern and color.

  var message: AttributedString {
        var result = AttributedString("Test string 1 2 3!")
        result.font = .largeTitle
        result.foregroundColor = .white
        result.backgroundColor = .blue
        result.underlineStyle = Text.LineStyle(pattern: .solid, color: .white)
        return result
    }

    var body: some View {
        Text(message)
    }

You can adjust the baseline offset for pieces of the string, forcing it to be placed higher or lower than default, providing you with better control:

    var message: AttributedString {
          let plainString = "These letters can go up and down"
          var result = AttributedString()

          for (index, letter) in plainString.enumerated() {
              var letterString = AttributedString(String(letter))
              letterString.baselineOffset = sin(Double(index)) * 5
              result += letterString
          }

          result.font = .largeTitle
          return result
      }

      var body: some View {
          Text(message)
      }

Link property can be used to add tappable web links in code.

    var message: AttributedString {
           var result = AttributedString("Learn SwiftUI here")
           result.font = .largeTitle
           result.link = URL(string: "https://www.google.com")
           return result
       }

       var body: some View {
           Text(message)
       }

However, the really powerful feature of AttributedString is that it doesn’t throw away all the metadata we provide it about our strings, which unlocks a huge amount of extra functionality.

For example, we can mark part of the string as needing to be spelled out for accessibility reasons, so that things like passwords are read out correctly when using VoiceOver

    var message: AttributedString {
         var password = AttributedString("AsDfghI@123")
         password.accessibilitySpeechSpellsOutCharacters = true
         return "Your password is: " + password
     }

     var body: some View {
         Text(message)
     }

 If we format a Date instance as an attributed string it retains knowledge of what each component represents – it remembers that “December” is the month part of the string, for example.

This means we can style our strings semantically: we can say “make the whole have a secondary color, apart from the weekday part – that should have a primary color”, like this:

    var message: AttributedString {
        var result = Date.now.formatted(.dateTime.weekday(.wide).day().month(.wide).attributed)
        result.foregroundColor = .secondary

        let weekday = AttributeContainer.dateField(.weekday)
        let weekdayStyling = AttributeContainer.foregroundColor(.primary)
        result.replaceAttributes(weekday, with: weekdayStyling)

        return result
    }

    var body: some View {
        Text(message)
    }

Notice how that code has no idea where the weekday actually appears in the text – it’s language and locale independent, so it will be styled correctly for everyone.

The same is true of working with the names of people using PersonNameComponents – this makes an AttributedString instance where the family name of someone is bold:

    var message: AttributedString {
          var components = PersonNameComponents()
          components.givenName = "Taylor"
          components.familyName = "Swift"

          var result = components.formatted(.name(style: .long).attributed)

          let familyNameStyling = AttributeContainer.font(.headline)
          let familyName = AttributeContainer.personNameComponent(.familyName)
          result.replaceAttributes(familyName, with: familyNameStyling)

          return result
      }

      var body: some View {
          Text(message)
      }

You can even use it with measurements. For example, the following code creates a measurement of 200 kilometers, then formats that so that the value is presented much larger than the unit:

    var message: AttributedString {
           var amount = Measurement(value: 200, unit: UnitLength.kilometers)
           var result = amount.formatted(.measurement(width: .wide).attributed)

           let distanceStyling = AttributeContainer.font(.title)
           let distance = AttributeContainer.measurement(.value)
           result.replaceAttributes(distance, with: distanceStyling)

           return result
       }

       var body: some View {
           Text(message)
       }

The above code will automatically switch to user’s preference while choosing the unit of measurement, like Kilometer to Miles.

One thought on “How to add advanced text styling using AttributedString in swiftUI?

Leave a Reply

Your email address will not be published. Required fields are marked *