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.
Search
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.