Enabling Genmoji in your app

Enabling Genmoji in your app

Learn how to make your app able to input custom emoji.

With iOS 18.0, iPadOS 18.0 and macOS 15.0, Apple introduced the adaptiveImageGlyph property to the NSAttributedStringenabling the class to store Genmoji, an emoji generated from textual prompts that, unlike emojis, are image glyphs and not Unicode.

The adaptiveImageGlyph property is of the NSAdaptiveImageGlyph type, a data object for an emoji-like image that can appear in attributed text, and has the following properties:

Every time a new custom emoji is created, TextKit creates an instance of NSAdaptiveImageGlyph storing Genmoji information in it, and enabling the ability to change its size and resolution - square aspect ratio based - according to the metadata; this later is also responsible for giving directions on how to correctly do it based on the text font and the font attributes.

Thanks to this capability to adapt to the text surrounding them, Genmoji are particularly appropriate for blog posts, titles and messages, which also means that they won't fit other cases like being used in identifiers, phone numbers or email addresses.

They can be used all alone or in combination with text, formatted, copied and pasted and used as stickers.

They are supported by rich text view and by the following system serializing frameworks - that have been updated, to natively support NSAdaptiveImageGlyph, with the introduction of NSAttributedString(adaptiveImageGlyph:, attributes:):

  • RTFD
  • UIActivity / UIPasteboard
  • NSPasteboard
  • HTML - NSAttributeString

Enabling the generation of Genmoji on the keyboard

The Genmoji generation icon button doesn’t show up automatically on your keyboard when you implement a TextEditoror a TextField .

To enable it, the property supportsAdaptiveImageGlypgh of the UITextView must be set to true.

// 1. Custom Text View
struct CustomTextEditor: UIViewRepresentable {

    @Binding var text: String
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.text = text
        // 2. Enabling editability 
        textView.isEditable = true
        textView.font = UIFont.systemFont(ofSize: 40)
        
        // 3. Enabling the genmoji creation 
        textView.supportsAdaptiveImageGlyph = true
                
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        if uiView.text != text {
            uiView.text = text
        }
    }
}
  1. Create a custom UITextView.
  2. Make it editable.
  3. Set the supportsAdaptiveImageGlypgh property to true.

And now create an instance of your CustomTextEditor in your main view.

import SwiftUI

struct ContentView: View {
    @State var text: String = ""
    
    var body: some View {
        VStack {
            CustomTextEditor(text: $text)
        }
        .padding()
    }
}
0:00
/0:33

The same result can be achieved when using NSAttributedStringrather thanString.

struct CustomTextEditor: UIViewRepresentable {
	
    // 1. The binding NSAttributedString text 
    @Binding var text: NSAttributedString?
    
    func makeUIView(context: Context) -> UITextView {
        
        ...
        
        // 2. Assign the binding NSAttributedString text 
        textView.attributedText = text
        
        // 3. Enabling the genmoji creation 
        textView.supportsAdaptiveImageGlyph = true

        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        if uiView.attributedText != text {
            uiView.attributedText = text
        }
    }
}
  1. Declare the binding NSAttributedString text variable that will store the input.
  2. Assign the binding NSAttributedString text to the attributedText property of your custom UITextView .
  3. Enable the Genmoji creation with supportsAdaptiveImageGlyph on true.

Create an instance of your CustomTextEditor in your main view.

import SwiftUI

struct ContentView: View {
    @State var text: NSAttributedString? = nil
    
    var body: some View {
        VStack {
            CustomTextEditor(text: $text)
        }
        .padding()
    }
}

Enabling the copy-paste of Genmoji from the edit menu

To be also able to copy-paste the Genmoji from the edit menu of your custom UITextView, be sure to set the allowsEditingTextAttributes property to true.

func makeUIView(context: Context) -> UITextView {
    ...
    
    // Enable its editability
    textView.allowsEditingTextAttributes = true
    
    ...
}

It also enables the display of the Genmoji button icon and feature.

0:00
/0:47

In macOS, NSTextView.importsGraphics allows you to achieve the same result.

struct CustomTextEditor: NSViewRepresentable {

    func makeNSView(context: Context) -> NSTextView {
        // 1. Instance of NSTextView
        let textView = NSTextView()
        
        ...
        
        // 2. Enable graphics
        textView.importsGraphics = true
       
        return textView
    }

}
  1. Create an instance of NSTextView.
  2. Set the importsGraphics property on true to enable the copy-paste of Genmoji.
0:00
/0:12

Serializing and de-serializing Genmoji for reading and displaying

It may happen in certain situations that you need to display the content of a text containing the Genmoji. To achieve this kind of result, the attributed string must be read and serialized as rich text formatted data to be ready to be shared.

This approach will ensure that all the data about the Genmoji will be preserved to avoid losing important information when recreating your NSAdaptiveImageGlyph when you are ready to display it again.

// 1. Access the attributed string
let textContents = textView.textStorage

// 2. Serialize as data 
let rtfData = try textContents.data(
    from: NSRange(location: 0, length: textContents.length),
    documentAttributes: [.documentType: NSAttributedString.DocumentType.rtfd]
)
  1. Store the attributed string from the textStorage property of your custom UITextView;
  2. Serialize it using the method data(from:documentAttributes:) , that turns a text stream corresponding to the characters and attributes within the specified range into a data object.

To display this data object, reverse the process.

// 1. De-serialized rtfData
let attributedString = try NSAttributedString(data: rtfData, documentAttributes: nil)

// 2. Set it on text view
textView.textStorage.setAttributedString(attributedString)
  1. Create and store an instance of NSAttributedString using the init(data:options:documentAttributes:) out from the rtfData.
  2. Set the returning NSAttributedString on the custom UITextView .

Let’s see how to start taking advantage of this approach by integrating it in a SwiftUI view.

Create a custom UITextView that will be responsible to display the NSAttributedString coming from the deserialization of the rtfData.

import SwiftUI

// Custom View
struct CustomTextView: UIViewRepresentable {
    
    @Binding var text: NSAttributedString?

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        
        ...
        
        return textView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        // Update the text view's content if the binding changes
        if let updatedText = text {
            uiView.attributedText = updatedText
        }
    }
}

Create a custom UITextView that will allow the user to input text and generate custom emoji.

// Custom Text Editor
struct CustomTextEditor: UIViewRepresentable {
    @Binding var text: NSAttributedString?

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.isEditable = true
        textView.font = UIFont.systemFont(ofSize: 16)
        textView.allowsEditingTextAttributes = true
        textView.supportsAdaptiveImageGlyph = true
        
        textView.delegate = context.coordinator
        
        // Initialize with the current text if available
        if let initialText = text {
            textView.attributedText = initialText
        }

        return textView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        if uiView.attributedText != text {
            uiView.attributedText = text
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UITextViewDelegate {
        var parent: CustomTextEditor
        
        init(_ parent: CustomTextEditor) {
            self.parent = parent
        }

        func textViewDidChange(_ textView: UITextView) {
            // Update the binding
            if let currentText = textView.attributedText {
                parent.text = currentText
            }
        }
    }
}

Be sure to implement Coordinator and assign it as the UITextView.delegate to handle text changes and sync them with the SwiftUI state.

struct ContentView: View {

    var body: some View {
        ...
    }
    
    // 1. Serialize text from an NSTextStorage into RTFD data
    func serializeText(text: NSAttributedString?) -> Data? {
        guard let text = text else { return nil }
        do {
            let rtfData = try text.data(from: NSRange(location: 0, length: text.length), documentAttributes: [.documentType: NSAttributedString.DocumentType.rtfd])
            return rtfData
        } catch {
            print("Error serializing text: \(error)")
            return nil
        }
    }

    // 2. Deserialize RTFD data back into an NSAttributedString
    func deserializeText(data: Data) -> NSAttributedString? {
        do {
            let attributedString = try NSAttributedString(data: data, documentAttributes: nil)
            return attributedString
        } catch {
            print("Error deserializing text: \(error)")
            return nil
        }
    }
}

Create two different functions:

  1. serializeText(text:) responsible for the serialization of NSAttributedString into Data;
  2. deserializeText(data:) responsible for the de-serialization ofData into NSAttributedString.
import SwiftUI

struct ContentView: View {
    @State var textInput: NSAttributedString? = NSAttributedString(string: "Type here")
    @State var textToDisplay: NSAttributedString? = nil

    var body: some View {
        VStack {
            // Custom Text Editor
            CustomTextEditor(text: $textInput)
                        
            // Custom Text View to display the serialized/deserialized content
            if let displayedText = textToDisplay {
                CustomTextView(text: $textToDisplay)
            } else {
                Text("No text to display")
                    .foregroundColor(.gray)
            }
        }
        .onChange(of: textInput) { oldText, newText in
            if let rtfData = serializeText(newText) {
                textToDisplay = deserializeText(rtfData)
            }
        }
        .padding()
    }
    
    func serializeText(text: NSAttributedString?) -> Data? {...}
    func deserializeText(data: Data) -> NSAttributedString? {...}
}

In this view, whenever the text input by the user has changed, the new value will be serialized into a Data object and de-serialized into an NSAttributedString to be displayed in a customUITextView.

0:00
/0:42

The introduction of the adaptiveImageGlyph property and NSAdaptiveImageGlyph in iOS 18.0, iPadOS 18.0, and macOS 15.0 allows to handle rich text with custom, adaptive emojis like Genmoji. Their seamless adaptability to surrounding text attributes, combined with their flexibility to serialize and deserialize across platforms, makes them a powerful tool for creating engaging user content.