Exporting and restoring AttributedString in rich TextEditor (iOS 26)

I am working with the rich TextEditor introduced in iOS 26, but I am having trouble preserving AttributedString formatting when converting to/from RTF.

Here is my exporting logic in my view model (AttributedString to RTF)

let nsAttrStr = NSAttributedString(self.text) // text is an AttributedString (bound to the TextEditor input)
        
let range = NSRange(location: 0, length: nsAttrStr.length)
        
let options: [NSAttributedString.DocumentAttributeKey: Any] = [
    .documentType: NSAttributedString.DocumentType.rtf
]
        
guard let data = try? nsAttrStr.data(from: range, documentAttributes: options) else {
    return nil
}
        
let rtfBase64 = data.base64EncodedString()

When I inspect the result, it seems to lose the font, size, boldness, etc which is being correctly rendered in the TextEditor. When I convert back from RTF to an AttributedString, it reverts to the default text formatting applied in the TextEditor.

Any ideas what could be going wrong?

Answered by DTS Engineer in 862834022

Yeah, converting AttributedString to NSAttributedString and then to Data with NSAttributedString.DocumentType.rtf will lose SwiftUI attributes (because SwiftUI attributes aren't represented in an ObjC-based NSAttributedString), unless you convert the SwiftUI attributes to UIKit / AppKit attributes with your own code.

You can convert AttributedString directly to RTF / RTFD data using AttributedTextFormatting.Transferable, which was newly introduced in iOS 26 and friends. The following code example shows how to do that:

struct ContentView: View {
    @Environment(\.self) private var environment
    ...
    
    private func attributedStringToRTF() async {    
        var attributedString = AttributedString(inputText)
        attributedString.backgroundColor = .red
        print("Input: attributedString = \(attributedString)")

        let transferable = AttributedTextFormatting.Transferable(text: attributedString, in: environment)
        guard let rtfData = try? await transferable.exported(as: .rtf) else {
            return
        }
        guard let transferrable2 = try? await AttributedTextFormatting.Transferable(importing: rtfData, contentType: .rtf) else {
            return
        }
        let attributedString2 = try! AttributedString(transferable: transferrable2, in: environment)
        print("Output: attributedString = \(attributedString2)")
    }
}

The output:

Input: attributedString = This is test. {
	SwiftUI.BackgroundColor = red
}
Output: attributedString = This is test. {
	SwiftUI.BackgroundColor = UIKitPlatformColorProvider(platformColor: kCGColorSpaceModelRGB 1 0.21961 0.23529 1)
	SwiftUI.Font = Font(provider: SwiftUI.FontBox<SwiftUI.Font.PlatformFontProvider>)
	SwiftUI.ForegroundColor = UIKitPlatformColorProvider(platformColor: kCGColorSpaceModelRGB 0 0 0 1)
	CoreText.TextAlignment = left
}

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Yeah, converting AttributedString to NSAttributedString and then to Data with NSAttributedString.DocumentType.rtf will lose SwiftUI attributes (because SwiftUI attributes aren't represented in an ObjC-based NSAttributedString), unless you convert the SwiftUI attributes to UIKit / AppKit attributes with your own code.

You can convert AttributedString directly to RTF / RTFD data using AttributedTextFormatting.Transferable, which was newly introduced in iOS 26 and friends. The following code example shows how to do that:

struct ContentView: View {
    @Environment(\.self) private var environment
    ...
    
    private func attributedStringToRTF() async {    
        var attributedString = AttributedString(inputText)
        attributedString.backgroundColor = .red
        print("Input: attributedString = \(attributedString)")

        let transferable = AttributedTextFormatting.Transferable(text: attributedString, in: environment)
        guard let rtfData = try? await transferable.exported(as: .rtf) else {
            return
        }
        guard let transferrable2 = try? await AttributedTextFormatting.Transferable(importing: rtfData, contentType: .rtf) else {
            return
        }
        let attributedString2 = try! AttributedString(transferable: transferrable2, in: environment)
        print("Output: attributedString = \(attributedString2)")
    }
}

The output:

Input: attributedString = This is test. {
	SwiftUI.BackgroundColor = red
}
Output: attributedString = This is test. {
	SwiftUI.BackgroundColor = UIKitPlatformColorProvider(platformColor: kCGColorSpaceModelRGB 1 0.21961 0.23529 1)
	SwiftUI.Font = Font(provider: SwiftUI.FontBox<SwiftUI.Font.PlatformFontProvider>)
	SwiftUI.ForegroundColor = UIKitPlatformColorProvider(platformColor: kCGColorSpaceModelRGB 0 0 0 1)
	CoreText.TextAlignment = left
}

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Exporting and restoring AttributedString in rich TextEditor (iOS 26)
 
 
Q