Skip to content
PressboardKit
EmojiBrowse docs

Guides

Emoji

The emoji panel — showing it, sharing recents across app and extension, skin tones, and full-text search.

PressboardKitEmoji is an optional product carrying the emoji panel: the catalog, the category tabs, skin tones, a recents store and full-text search. Leave it out of a keyboard that has no emoji key and none of it is linked.

If you subclass PressboardInputViewController, the panel is already wired, including its taller height and the search header. This page is for hosts assembling the keyboard themselves, and for anyone who wants to change what the panel does.

Showing the panel

The emoji page is a KeyboardType, so it flows through the same state as every other page. The view renders whatever you put in its emojiKeyboard slot when layout.keyboardType == .emojis — set that when you receive .keyboardType(.emojis).

import PressboardKitEmoji

PressboardKeyboardView(
    layout: model.layout,
    behavior: model.behavior,
    onAction: handle,
    emojiKeyboard: {
        EmojiKeyboard(
            recents: recentsStore.recents(),
            onSelect: { emoji in
                insert(emoji)
                recentsStore.markUsed(emoji)
            },
            onBackspace: { delete() },
            onABC: { model.setPage(.alphabetic) })
    })

EmojiKeyboard is a UIViewRepresentable over a UIKit grid, which is what keeps scrolling several thousand glyphs cheap inside an extension. Its other parameters are optional: accentColor for the active category tab, onSelectAt if you want the tap’s rect so you can place a preview at the finger, onFeedback for a haptic, and emojiRow to select the design snapshot’s category-row style.

The emojiKey parameter controls whether the smiley key is shown at all. Pass it to the resolver along with the other key-visibility toggles.

Recents

EmojiRecentsStore is a thin wrapper over UserDefaults. Pass your App Group suite and the app and the extension share one history:

let recentsStore = EmojiRecentsStore(defaults: BehaviorStore.defaults)

recentsStore.recents()          // most-recent first
recentsStore.markUsed("🎉")

It takes an optional key and maxRecents (30 by default). Until the user has built up a history, the panel falls back to EmojiCatalog.defaultRecents — a set of the most commonly used emoji, rather than an empty tab.

With PressboardKitApp this is emojiDefaults on PressboardConfiguration, and the controller exposes emojiRecents() and markEmojiUsed(_:).

Skin tones

EmojiSkinTone handles the five Fitzpatrick modifiers:

EmojiSkinTone.supportsSkinTones("👍")   // true
EmojiSkinTone.variants(of: "👍")        // the base emoji plus its five toned forms

The panel uses these for the long-press tone picker.

Full-text search is gated by emojiSearch and is pure, so it is testable and cheap:

EmojiSearch.search("party")     // -> ["🎉", "🥳", …]

Matching is case- and diacritic-insensitive over a curated keyword map in English and Czech, so “stastny” finds the same emoji as “šťastný”.

The search field itself is EmojiSearchHeader, and it goes in the view’s toolbar slot — it replaces the suggestion bar while the emoji page is up:

toolbar: {
    if behavior.emojiSearch && layout.keyboardType == .emojis {
        EmojiSearchHeader(
            isActive: model.emojiSearchActive,
            query: model.emojiQuery,
            results: model.emojiResults,
            onActivate: { model.activateEmojiSearch() },
            onPick: { model.insertEmoji($0) })
    } else {
        SuggestionBar(suggestions: model.suggestions, onPick: applyPick)
    }
}

While search is active, route .character and .backspace into the query rather than into the document, and let .primary close the search. PressboardController’s emoji-search methods — activateEmojiSearch(), exitEmojiSearch(), insertEmoji(_:) — are the reference.

Sizing

The emoji page is taller than the letters page, and the search header is taller than the suggestion bar. EmojiSearchHeader.height is the full height including its results row; browseHeight is the field row alone. Fold both into your height calculation — see Performance.

Next: Performance.

Edit this page on GitHub