With WWDC 2021, Apple introduced a long-awaited feature for developers called AttributedString, which means Swift developers no longer need to use Objective-C NSAttributedString to create styled text. This article will give you a thorough introduction and show you how to create custom properties.

The original post was posted on my blog wwww.fatbobman.com

Public number: [Swift Notepad for elbow]

First impression

An AttributedString is a string with a single character or a range of character attributes. Properties provide characteristics such as visual style for display, accessibility for boot, and hyperlinked data for linking between data sources.

The following code generates an attribute string with bold text and hyperlinks.

var attributedString = AttributedString("Visit Elbow's blog.")
let zhouzi = attributedString.range(of: "Elbow")!  // Get the Range of the elbow subword
attributedString[zhouzi].inlinePresentationIntent = .stronglyEmphasized // Set properties -- bold
let blog = attributedString.range(of: "Blog")! 
attributedString[blog].link = URL(string: "https://www.fatbobman.com")! // Set the property -- hyperlink
Copy the code

Before WWDC 2021, SwiftUI did not provide support for attribute strings, and if we wanted to display text with rich styles, we usually did so in one of three ways:

  • Wrap UIKit or AppKit controls as SwiftUI controls, where NSAttributedString is displayed
  • The NSAttributedString is converted into the corresponding SwiftUI layout code by code
  • Combined display using SwiftUI’s native controls

The following text increases as the SwiftUI version changes (without using NSAttributedString) :

SwiftUI 1.0

    @ViewBuilder
    var helloView:some View{
        HStack(alignment:.lastTextBaseline, spacing:0) {Text("Hello").font(.title).foregroundColor(.red)
            Text(" world").font(.callout).foregroundColor(.cyan)
        }
    }
Copy the code

SwiftUI 2.0

SwiftUI 2.0 has enhanced the function of Text, we can combine different Text with + display

    var helloText:Text {
        Text("Hello").font(.title).foregroundColor(.red) + Text(" world").font(.callout).foregroundColor(.cyan)
    }
Copy the code

SwiftUI 3.0

In addition to the above methods, Text adds native support for AttributedString

    var helloAttributedString:AttributedString {
        var hello = AttributedString("Hello")
        hello.font = .title.bold()
        hello.foregroundColor = .red
        var world = AttributedString(" world")
        world.font = .callout
        world.foregroundColor = .cyan
        return hello + world
    }

		Text(helloAttributedString)
Copy the code

Simply looking at the example above, you don’t see any advantage in AttributedString. As you continue reading this article, you’ll see that AttributedString can do a lot more than it could have done before.

AttributedString vs NSAttributedString

AttributedString can basically be viewed as a Swift implementation of NSAttributedString, with little difference in functionality and inherent logic. However, there are still many differences between them due to the age of formation, core code language, etc. This section will compare them in several ways.

type

AttributedString is a value type, which is the biggest difference between it and NSAttributedString (a reference type) built in Objective-C. This means that it can be passed, copied, and changed just like any other value through Swift’s value semantics.

NSAttributedString mutable or immutable needs a different definition

let hello = NSMutableAttributedString("hello")
let world = NSAttributedString(" world")
hello.append(world)
Copy the code

AttributedString

var hello = AttributedString("hello")
let world = AttributedString(" world")
hello.append(world)
Copy the code

security

In AttributedString, you need to use Swift’s point or key syntax to access attributes by name, both for type-safety and for the benefit of compile-time checking.

The following attribute access methods of NSAttributedString are not used in AttributedString, which greatly reduces the probability of errors

// A type mismatch may occur
let attributes: [NSAttributedString.Key: Any] = [
    .font: UIFont.systemFont(ofSize: 72),
    .foregroundColor: UIColor.white,
]
Copy the code

Localization support

View provides native localized string support and can add specific attributes to localized strings.

var localizableString = AttributedString(localized: "Hello \(Date.now,format: .dateTime) world",locale: Locale(identifier: "zh-cn"),option:.applyReplacementIndexAttribute)
Copy the code

The Formatter support

The new Formatter API, also available in WWDC 2021, fully supports AttributedString formatted output. We can easily do things that we couldn’t do in the past.

var dateString: AttributedString {
        var attributedString = Date.now.formatted(.dateTime
            .hour()
            .minute()
            .weekday()
            .attributed
        )
        let weekContainer = AttributeContainer()
            .dateField(.weekday)
        let colorContainer = AttributeContainer()
            .foregroundColor(.red)
        attributedString.replaceAttributes(weekContainer, with: colorContainer)
        return attributedString
}

Text(dateString)
Copy the code

For more examples of how the new Formatter API works with AttributedString, see WWDC 2021 New Formatter API: Old vs. New and How to Customize it

SwiftUI integration

The Text component of SwiftUI provides native support for AttributedString, which ameliorates a long-standing SwiftUI pain point (TextField and TextEdit are still not supported, though).

AttributedString provides SwiftUI, UIKit, and AppKit attributes. UIKit or AppKit controls can also render AttributedString (after conversion).

Supported file formats

AttributedString currently only has the ability to parse Markdown formatted text. It’s still a far cry from NSAttributedString’s support for Markdown, RTF, DOC, and HTML.

conversion

Apple provides the ability to convert AttributedString and NSAttributedString to each other.

// AttributedString -> NSAttributedString
let nsString = NSMutableAttributedString("hello")
var attributedString = AttributedString(nsString)

// NSAttribuedString -> AttributedString
var attString = AttributedString("hello")
attString.uiKit.foregroundColor = .red
let nsString1 = NSAttributedString(attString)
Copy the code

Developers can take advantage of the strengths of both. Such as:

  • Parsing the HTML with NSAttributedString, and then converting it into an AttributedString call
  • Create a type-safe string using AttributedString, which is converted to NSAttributedString when displayed

basis

In this section, we introduce some of the most important concepts in AttributedString and show you some more uses of AttributedString through code snippets.

AttributedStringKey

AttributedStringKey defines the name and type of the AttributedString attribute. Quick access with type-safety can be achieved by clicking on the syntax or KeyPath.

var string = AttributedString("hello world")
// Use dot syntax
string.font = .callout
let font = string.font 

/ / use KeyPath
let font = string[keyPath:\.font] 
Copy the code

In addition to using a number of preset properties, we can also create our own. Such as:

enum OutlineColorAttribute : AttributedStringKey {
    typealias Value = Color // Attribute type
    static let name = "OutlineColor" // Attribute name
}

string.outlineColor = .blue
Copy the code

We could use some grammar or KeyPath for AttributedString, AttributedSubString, AttributeContainer and AttributedString. Runs. The attribute of the Run. See other code snippets in this article for more usage.

AttributeContainer

AttributeContainer is an AttributeContainer. By configuring Containers, you can set, replace, and merge a large number of attributes for an attribute string (or fragment) at once.

Set properties

var attributedString = AttributedString("Swift")
string.foregroundColor = .red 

var container = AttributeContainer()
container.inlinePresentationIntent = .strikethrough
container.font = .caption
container.backgroundColor = .pink
container.foregroundColor = .green // Will override the original red

attributedString.setAttributes(container) // attributdString now has four attributes
Copy the code

Replace the attribute

var container = AttributeContainer()
container.inlinePresentationIntent = .strikethrough
container.font = .caption
container.backgroundColor = .pink
container.foregroundColor = .green
attributedString.setAttributes(container)
// The attributedString has four attributes: font, backgroundColor, foregroundColor, and inlinePresentationIntent

// The attribute to be replaced
var container1 = AttributeContainer()
container1.foregroundColor = .green
container1.font = .caption

// The attribute to be replaced
var container2 = AttributeContainer()
container2.link = URL(string: "https://www.swift.org")

// The replaced attribute contianer1 can be replaced only if its key values are all consistent, for example, the foregroundColor of continaer1 is. Red will not be replaced
attributedString.replaceAttributes(container1, with: container2)
// After the replacement, the attributedString has three attributes: backgroundColor, inlinePresentationIntent, and link
Copy the code

Merge attributes

var container = AttributeContainer()
container.inlinePresentationIntent = .strikethrough
container.font = .caption
container.backgroundColor = .pink
container.foregroundColor = .green
attributedString.setAttributes(container)
// The attributedString has four attributes: font, backgroundColor, foregroundColor, and inlinePresentationIntent

var container2 = AttributeContainer()
container2.foregroundColor = .red
container2.link = URL(string: "www.swift.org")

attributedString.mergeAttributes(container2,mergePolicy: .keepNew)
// The merged attributedString has five attributes: font, backgroundColor, foregroundColor, inlinePresentationIntent, and link
/ / the foreground for red
// when properties conflict, use mergePolicy to select the mergePolicy.keepnew (default) or.keepcurrent
Copy the code

AttributeScope

Attribute range is a set of attributes defined by the system framework. Attributes suitable for a specific domain are defined in a range. On the one hand, it is easy to manage, and on the other hand, it solves the problem of inconsistent corresponding types of the same attribute name in different frameworks.

Currently, AttributedString provides five preset scopes, one for each

  • foundation

    Contains properties for Formatter, Markdown, URL, and language distortion

  • swiftUI

    Properties that can be rendered under SwiftUI, such as foregroundColor, backgroundColor, font, etc. Currently supports significantly fewer properties than uiKit and appKit. It is expected that SwiftUI will provide more display support in the future and gradually replace the other properties that are not supported.

  • uiKit

    Properties that can be rendered under UIKit.

  • appKit

    Properties that can be rendered in AppKit

  • accessibility

    Barrier-friendly properties to improve the availability of bootstrap access.

There are many properties of the same name (such as foregroundColor) in the three scopes swiftUI, uiKit and appKit. Note the following when accessing:

  • When Xcode cannot correctly infer which Scope attributes apply, explicitly indicate the corresponding AttributeScope
uiKitString.uiKit.foregroundColor = .red //UIColor
appKitString.appKit.backgroundColor = .yellow //NSColor
Copy the code
  • The attributes of the same name of the three frames cannot be transferred to each other. If you want strings to support multi-frame display (code reuse), please assign values to the attributes of the same name of different scopes
attributedString.swiftUI.foregroundColor = .red
attributedString.uiKit.foregroundColor = .red
attributedString.appKit.foregroundColor = .red

// Convert to NSAttributedString, which can only convert the specified Scope attribute
let nsString = try! NSAttributedString(attributedString, including: \.uiKit)
Copy the code
  • To improve compatibility, some of the same properties can be set in Foundation.
attributedString.inlinePresentationIntent = .stronglyEmphasized // equivalent to bold
Copy the code
  • The definitions of swiftUI, uiKit, and appKit include foundation and accessibility respectively. Therefore, foundation and accessibility attributes can be converted normally even if only a single framework is specified during conversion. It is a good idea to follow this principle when customizing scopes.
let nsString = try! NSAttributedString(attributedString, including: \.appKit)
// Foundation and accessibility attributes in attributedString are also converted
Copy the code

view

In attribute strings, attributes and text can be accessed independently, and AttributedString provides three views that allow developers to access the content they want from another dimension.

Character and unicodeScalar views

These two views provide functionality similar to the String attribute of NSAttributedString, allowing developers to manipulate data in the dimension of plain text. The only difference between the two views is their type. In a nutshell, you can think of a ChareacterView as a Charecter collection and a UnicodeScalarView as a Unicode scalar collection.

String length

var attributedString = AttributedString("Swift")
attributedString.characters.count / / 5
Copy the code

The length of the 2

let attributedString = AttributedString("Hello 👩 🏽 ‍ 🦳")
attributedString.characters.count / / 7
attributedString.unicodeScalars.count / / 10
Copy the code

Convert to string

String(attributedString.characters) // "Swift"
Copy the code

Substitution string

var attributedString = AttributedString("hello world")
let range = attributedString.range(of: "hello")!
attributedString.characters.replaceSubrange(range, with: "good")
// Good world, the replacement good retains all attributes of hello's location
Copy the code

Runs view

Attribute view of AttributedString. Each Run corresponds to a string fragment with exactly the same attributes. Use for-in syntax to iterate over the runs attribute of AttributedString.

** has only one Run **

All character attributes are consistent throughout the attribute string

let attributedString = AttribuedString("Core Data")
print(attributedString)
// Core Data {}
print(attributedString.runs.count) / / 1
Copy the code

Two Run

The property strings coreData, Core, and Data segments have different properties, resulting in two runs

var coreData = AttributedString("Core")
coreData.font = .title
coreData.foregroundColor = .green
coreData.append(AttributedString(" Data"))

for run in coreData.runs { //runs.count = 2
    print(run)
}

// Core { 
// SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5cd3a0a0).FontBox
      
       )
      (unknown>
// SwiftUI.ForegroundColor = green
/ /}
// Data {}
Copy the code

Multiple Run

var multiRunString = AttributedString("The attributed runs of the attributed string, as a view into the underlying string.")
while let range = multiRunString.range(of: "attributed") {
    multiRunString.characters.replaceSubrange(range, with: "attributed".uppercased())
    multiRunString[range].inlinePresentationIntent = .stronglyEmphasized
}
var n = 0
for run in multiRunString.runs {
    n + = 1
}
// n = 5
Copy the code

End result: The root runs of The root string, as a view into The underlying string.

Use Run’s range to set properties

// Continue with the multiRunString above
// Set all unemphasized characters to yellow
for run in multiRunString.runs {
    guard run.inlinePresentationIntent ! = .stronglyEmphasized else {continue}
    multiRunString[run.range].foregroundColor = .yellow
}
Copy the code

Gets the specified attribute through Runs

// Change the yellow bold text to red
for (color,intent,range) in multiRunString.runs[\.foregroundColor,\.inlinePresentationIntent] {
    if color = = .yellow && intent = = .stronglyEmphasized {
        multiRunString[range].foregroundColor = .red
    }
}
Copy the code

Collect all used attributes through the Attributes of Run

var totalKeysContainer = AttributeContainer(a)for run in multiRunString.runs{
    let container = run.attributes
    totalKeysContainer.merge(container)
}
Copy the code

Using the Runs view makes it easy to get the information you need from many attributes

Do not use the Runs view to achieve a similar effect

multiRunString.transformingAttributes(\.foregroundColor,\.font){ color,font in
    if color.value = = .yellow && font.value = = .title {
        multiRunString[color.range].backgroundColor = .green
    }
}
Copy the code

Although the Runs view is not called directly, the transformingAttributes closure is called at the same time as the Runs view. TransformingAttributes Supports obtaining a maximum of five attributes.

Range

In previous code in this article, you’ve used Range several times to access or modify the contents of an attribute string.

You can modify attributes of local content in an attribute string in two ways:

  • Through the Range
  • Through AttributedContainer

Get Range by keyword

// Look forward from the end of the attribute string and return the first range that satisfies the keyword (case ignored)
if let range = multiRunString.range(of: "Attributed", options: [.backwards, .caseInsensitive]) {
    multiRunString[range].link = URL(string: "https://www.apple.com")}Copy the code

Get a Range using Runs or transformingAttributes

This has been used repeatedly in previous examples

Get Range from this view

if let lowBound = multiRunString.characters.firstIndex(of: "r"),
   let upperBound = multiRunString.characters.firstIndex(of: ","),
   lowBound < upperBound
{
    multiRunString[lowBound.upperBound].foregroundColor = .brown
}
Copy the code

localization

Create a localized property string

// Localizable Chinese
"hello" = "Hello";
// Localizable English
"hello" = "hello";

let attributedString = AttributedString(localized: "hello")
Copy the code

In English and Chinese environments, the display will be Hello and hello, respectively

Currently, the localized AttributedString only displays the language set for the current system, not a specific language

var hello = AttributedString(localized: "hello")
if let range = hello.range(of: "h") {
    hello[range].foregroundColor = .red
}
Copy the code

The literal content of the localized string will vary with the system language, and the above code will not get the range in Chinese. It needs to be adapted for different languages.

replacementIndex

Can be set for the interpolation of the localized string content index (through applyReplacementIndexAttribute), go to the lavatory search in the localized content

// Localizable Chinese
"world %@ %@" = "% @ % @ in the world";
// Localizable English
"world %@ %@" = "world %@ %@";

var world = AttributedString(localized: "world \ ["👍") \ ["🥩")",options: .applyReplacementIndexAttribute) // When creating an attribute string, index is set in interpolation order, 👍 index == 1 🥩 index == 2

for (index,range) in world.runs[\.replacementIndex] {
    switch index {
        case 1:
            world[range].baselineOffset = 20
            world[range].font = .title
        case 2:
            world[range].backgroundColor = .blue
        default:
            world[range].inlinePresentationIntent = .strikethrough
    }
}
Copy the code

In The Chinese and English environments, respectively:

Sets the Formatter in string interpolation using locale

 AttributedString(localized: "\(Date.now, format: Date.FormatStyle(date: .long))", locale: Locale(identifier: "zh-cn"))
October 7, 2021 is displayed even in an English environment
Copy the code

Generate the property string using Formatter

        var dateString = Date.now.formatted(.dateTime.year().month().day().attributed)
        dateString.transformingAttributes(\.dateField) { dateField in
            switch dateField.value {
            case .month:
                dateString[dateField.range].foregroundColor = .red
            case .day:
                dateString[dateField.range].foregroundColor = .green
            case .year:
                dateString[dateField.range].foregroundColor = .blue
            default:
                break}}Copy the code

Markdown symbol

Since SwiftUI 3.0, Text has provided support for some Markdown tags. Similar functionality is provided in localized property strings, where the corresponding property is set. Provides greater flexibility.

var markdownString = AttributedString(localized: "**Hello** ~world~ _! _")
for (inlineIntent,range) in markdownString.runs[\.inlinePresentationIntent] {
    guard let inlineIntent = inlineIntent else {continue}
    switch inlineIntent{
        case .stronglyEmphasized:
            markdownString[range].foregroundColor = .red
        case .emphasized:
            markdownString[range].foregroundColor = .green
        case .strikethrough:
            markdownString[range].foregroundColor = .blue
        default:
            break}}Copy the code

Markdown parsing

AttributedString not only supports partial Markdown tags in localized strings, but also provides a complete Markdown parser.

Support for parsing Markdown text content from String, Data, or URL.

Such as:

let mdString = try! AttributedString(markdown: "# Title\n**hello**\n")
print(mdString)

// Parse the result
Title {
	NSPresentationIntent = [header 1 (id 1)]
}
hello {
	NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 2)
	NSPresentationIntent = [paragraph (id 2)]}Copy the code

After parsing, the text style and label are set in the inlinePresentationIntent and presentationIntent.

  • inlinePresentationIntent

    Character properties: such as bold, italic, code, reference, etc

  • presentationIntent

    Paragraph properties: such as paragraphs, tables, lists, etc. A presentationIntent may have multiple contents in a Run, which can be retrieved with a Component.

README.md

#  Hello# #Header2

hello **world**

* first
* second

> test `print("hello world") `| row1 | row2 |
| ---- | ---- |
| 34   | 135  |[newThe Formatter introduced] (/posts/newFormatter/)
Copy the code

Parsing code:

let url = Bundle.main.url(forResource: "README", withExtension: "md")!
var markdownString = try! AttributedString(contentsOf: url,baseURL: URL(string: "https://www.fatbobman.com"))
Copy the code

Analysis results (excerpt) :

Hello {
	NSPresentationIntent = [header 1 (id 1)]}Header2 {
	NSPresentationIntent = [header 2 (id 2)]
}
first {
	NSPresentationIntent = [paragraph (id 6), listItem 1 (id 5), unorderedList (id 4)]
}

test  {
	NSPresentationIntent = [paragraph (id 10), blockQuote (id 9)]}print("hello world") {
	NSPresentationIntent = [paragraph (id 10), blockQuote (id 9)]
	NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 4)
}
row1 {
	NSPresentationIntent = [tableCell 0 (id 13), tableHeaderRow (id 12), table [Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left), Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left)] (id 11)]
}
row2 {
	NSPresentationIntent = [tableCell 1 (id 14), tableHeaderRow (id 12), table [Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left), Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left)] (id 11New)]}The Formatter introduced {
	NSPresentationIntent = [paragraph (id 18)]
	NSLink = /posts/newFormatter/ -- https://www.fatbobman.com
}
Copy the code

Parsed content includes paragraph attributes, heading numbers, table column numbers, row numbers, alignment, and so on. Indentation, labeling, and other information can be handled in code by enumerating associated values.

The general code is as follows:

for run in markdownString.runs {
    if let inlinePresentationIntent = run.inlinePresentationIntent {
        switch inlinePresentationIntent {
        case .strikethrough:
            print("Delete line")
        case .stronglyEmphasized:
            print("Bold")
        default:
            break}}if let presentationIntent = run.presentationIntent {
        for component in presentationIntent.components {
            switch component.kind{
                case .codeBlock(let languageHint):
                    print(languageHint)
                case .header(let level):
                    print(level)
                case .paragraph:
                    let paragraphID = component.identity
                default:
                    break}}}}Copy the code

SwiftUI does not support rendering presentationIntent attachments. If you want to achieve the desired display, write your own visual style Settings code.

Custom attributes

Using custom attributes not only helps developers create attribute strings that better meet their own requirements, but also further reduces the coupling between information and code and improves flexibility by adding custom attribute information to Markdown text.

The basic flow of custom attributes is as follows:

  • Create a custom AttributedStringKey

    Create a opposed protocol datatype for each attribute you want to add.

  • Create a custom AttributeScope and extend AttributeScopes

    Create your own Scope and add all your custom properties to it. In order to facilitate the custom attribute set to be used when Scope needs to be specified, it is recommended to nest the required system framework Scope (swiftUI, uiKit, appKit) in the custom Scope. Add a custom Scope to AttributeScopes.

  • Extend AttributeDynamicLookup (support point syntax)

    Create a subscript method that matches the custom Scope in AttributeDynamicLookup. Dynamic support for point syntax and KeyPath.

Example 1: Create the ID attribute

In this case we will create an attribute named ID.

struct MyIDKey:AttributedStringKey {
    typealias Value = Int // The type of the property content. The type needs to be Hashable
    static var name: String = "id" // The name stored inside the property string
}

extension AttributeScopes{
    public struct MyScope:AttributeScope{
        let id:MyIDKey  // The name of the dot syntax call
        let swiftUI:SwiftUIAttributes // Add the system framework swiftUI to my Scope
    }

    var myScope:MyScope.Type{
        MyScope.self}}extension AttributeDynamicLookup{
    subscript<T> (dynamicMember keyPath:KeyPath<AttributeScopes.MyScope.T>) -> T where T:AttributedStringKey {
        self[T.self]}}Copy the code

call

var attribtedString = AttributedString("hello world")
attribtedString.id = 34
print(attribtedString)


// Output
hello world {
	id = 34
}
Copy the code

Example 2: Create enumerated properties and support Markdown resolution

If we want to create your own attribute can be resolved in the Markdown text, need to let the custom properties match CodeableAttributedStringKey and MarkdownDecodableAttributedStringKye

// The data type of the custom attribute is unlimited, as long as it satisfies the required protocol
enum PriorityKey:CodableAttributedStringKey.MarkdownDecodableAttributedStringKey{
    public enum Priority:String.Codable{ // To parse in Markdown, set the RAW type to String and Codable
        case low
        case normal
        case high
    }

    static var name: String = "priority"
    typealias Value = Priority
}

extension AttributeScopes{
    public struct MyScope:AttributeScope{
        let id:MyIDKey
        let priority:PriorityKey // Add the newly created Key to the custom Scope
        let swiftUI:SwiftUIAttributes
    }

    var myScope:MyScope.Type{
        MyScope.self}}Copy the code

Use ^[text](attribute name: attribute value) to mark custom attributes in Markdown

call

When parsing custom attributes in Markdown text, specify Scope.
var attributedString = AttributedString(localized: "^[hello world](priority:'low')",including: \.myScope)
print(attributedString)

// Output
hello world {
	priority = low
	NSLanguage = en
}
Copy the code

Example 3: Create a multi-parameter property

enum SizeKey:CodableAttributedStringKey.MarkdownDecodableAttributedStringKey{
    public struct Size:Codable.Hashable{
        let width:Double
        let height:Double
    }

    static var name: String = "size"
    typealias Value = Size
}

// Add it to Scope
let size:SizeKey
Copy the code

call

// Add multiple parameters to {}
let attributedString = AttributedString(localized: "^" hello world] (size: {width: 343.3, height: 200.3}, priority: 'high')",including: \.myScope)
print(attributedString)

// Output
ello world {
	priority = high
	size = Size(width: 343.3, height: 200.3)
	NSLanguage = en
}
Copy the code

In WWDC 2021’s New Formatter API, there are examples of using custom properties in Formatter

conclusion

Before AttributedString, most developers used attribute strings primarily to describe the display style of text, but with the ability to add custom attributes to Markdown text, it’s not long before developers extend AttributedString to more scenarios.

I hope this article has been helpful to you.

The original post was posted on my blog wwww.fatbobman.com

Public number: [Swift Notepad for elbow]