Instruction

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Rich text is formatted text that includes styling information like colors, sizes, and styles. Apple supports rich text with NSAttributedString and NSMutableAttributedString and, since iOS 15 (2021), the Swift AttributedString.

Enabling Rich Text Input

In SwiftUI, to get rich text input, you need a TextEditor, and you store the input in an AttributedString.

Storing Rich Text

There’s no easy way to serialize an AttributedString directly — you can convert it to an NSAttributedString, then use the following procedure.

// Extract contents of text view as an NSAttributedString
let textContents = textView.textStorage

// Serialize as data for storage or transport
let rtfData = try textContents.data(
  from: NSRange(location: 0, length: textContents.length),
  documentAttributes: [.documentType: NSAttributedString.DocumentType.rtfd]
)
// Create attributed string from serialized data
let textFromData = try NSAttributedString(data: rtfData, documentAttributes: nil)

// Set on text view
textView.textStorage.setAttributedString(textFromData)

Cross-Platform Genmoji Display: HTML

Again, when working with AttributedString, convert to NSAttributedString to perform the following function.

// Converting NSAttributedString to HTML
let htmlData = try textContent.data(
  from: NSRange(location: 0, length: textContent.length),
  documentAttributes: [.documentType: NSAttributedString.DocumentType.html]
)

Plain Text: Inline Image

If you need to transfer your image glyphs to plain text or other non-RTF data stores, you could store the Unicode attachment character NSAttachmentCharacter 0xFFFC at the appropriate text location, with a reference to the image glyph’s identifier in the plain text data field, and add the image to the image store. Because an image glyph’s contentIdentifier is unique and stable, you only need to store it once.

// Decompose an NSAttributedString
func decomposeAttributedString(_ attrStr: NSAttributedString)
  -> (String, [(NSRange, String)], [String: Data]) {
  // 1
  let string = attrStr.string
  var imageRanges: [(NSRange, String)] = []
  var imageData: [String: Data] = [:]
  // 2
  attrStr.enumerateAttribute(
    .adaptiveImageGlyph, 
    in: NSMakeRange(0, attrStr.length)) { (value, range, stop) in
    
    if let glyph = value as? NSAdaptiveImageGlyph {
      let id = glyph.contentIdentifier
      imageRanges.append((range, id))
      if imageData[id] == nil {
        imageData[id] = glyph.imageContent
      }
    }
  }
  // 3
  return (string, imageRanges, imageData)
}
// Recompose an attributed string
func recomposeAttributedString(string: String,
                               imageRanges: [(NSRange, String)],
                               imageData: [String: Data]) -> NSAttributedString {
  let attrStr: NSMutableAttributedString = .init(string: string)
  var images: [String: NSAdaptiveImageGlyph] = [:]
  for (id, data) in imageData {
    images[id] = NSAdaptiveImageGlyph(imageContent: data)
  }
  for (range, id) in imageRanges {
    attrStr.addAttribute(.adaptiveImageGlyph, value: images[id]!, range: range)
  }
  return attrStr
}
See forum comments
Download course materials from Github
Previous: Introduction Next: Demo