Skip to content

Instantly share code, notes, and snippets.

@AbodiDawoud
Created December 30, 2025 18:11
Show Gist options
  • Select an option

  • Save AbodiDawoud/7dcc0fdb5de72e7ef5282e457b6bd6d1 to your computer and use it in GitHub Desktop.

Select an option

Save AbodiDawoud/7dcc0fdb5de72e7ef5282e457b6bd6d1 to your computer and use it in GitHub Desktop.
Custom Emoji Picker View
import SwiftUI
@main
struct EmojiPickerDemoApp: App {
@State private var selectedEmoji: String = "🏴‍☠️"
@State private var showEmojiPopover: Bool = false
var body: some Scene {
WindowGroup {
ZStack {
VisualEffectBlur(material: .hudWindow, blendingMode: .behindWindow)
.ignoresSafeArea()
demoView
}
.frame(width: 275, height: 130, alignment: .center)
}
.windowResizability(.contentSize)
.windowStyle(.hiddenTitleBar)
}
private var demoView: some View {
HStack {
Button("Select Emoji") {
showEmojiPopover = true
}
.fontWeight(.semibold)
.buttonStyle(.plain)
Divider()
.opacity(0.8)
.padding(.vertical, 1.4).padding(.horizontal, 4)
Text(selectedEmoji)
.fixedSize(horizontal: true, vertical: false)
}
.padding(.horizontal, 20)
.padding(.vertical, 8)
.fixedSize(horizontal: false, vertical: true)
.overlay {
RoundedRectangle(cornerRadius: 8)
.stroke(.quaternary, lineWidth: 1)
}
.emojiPalette(selectedEmoji: $selectedEmoji, isPresented: $showEmojiPopover)
}
}
struct EmojiPaletteView: View {
@Binding var selectedEmoji: String
@State private var selectedCategory: String = EmojiProvider.recentsKey
private let columns: [GridItem] = Array(repeating: .init(.flexible(), spacing: 13), count: 7)
private let provider = EmojiProvider.shared
var body: some View {
NavigationStack {
List {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(provider.emojisFromCategory(selectedCategory), id: \.self) { emoji in
Button(emoji) {
selectedEmoji = emoji
print(selectedEmoji + ": " + selectedCategory)
}
.font(.system(size: 26))
.buttonStyle(.borderless)
.frame(width: 32, height: 32)
}
}
.listRowSeparator(.hidden)
.id(selectedCategory)
}
.listStyle(.plain)
.navigationTitle(provider.categoryLocalizedName(for: selectedCategory))
.toolbarTitleDisplayMode(.inline)
.toolbarBackgroundVisibility(.visible, for: .windowToolbar)
.safeAreaInset(edge: .bottom) {
HStack(spacing: 8) {
ForEach(provider.categories, id: \.self) { category in
Image(systemName: provider.getSFSymbol(for: category))
.font(.system(size: 18))
.frame(width: 24, height: 24)
.foregroundColor(category == selectedCategory ? Color.accentColor : .secondary)
.onTapGesture {
selectedCategory = category
}
}
}
.padding(8)
.background(.bar)
}
}
.frame(width: 300, height: 300)
}
}
extension View {
/// Presents an emoji picker in a popover modifier
func emojiPalette(selectedEmoji: Binding<String>,
isPresented: Binding<Bool>,
attachmentAnchor: PopoverAttachmentAnchor = .rect(.bounds),
arrowEdge: Edge = .top
) -> some View {
self.popover(isPresented: isPresented, attachmentAnchor: attachmentAnchor, arrowEdge: arrowEdge) {
EmojiPaletteView(selectedEmoji: selectedEmoji).presentationCompactAdaptation(.popover)
}
}
}
struct VisualEffectBlur: NSViewRepresentable {
var material: NSVisualEffectView.Material
var blendingMode: NSVisualEffectView.BlendingMode
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
view.material = material
view.blendingMode = blendingMode
view.state = .active
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
nsView.material = material
nsView.blendingMode = blendingMode
}
}
class EmojiProvider {
static let shared = EmojiProvider()
var categories: [String] {
let EMFEmojiCategory = NSClassFromString("EMFEmojiCategory") as! NSObject.Type
let list = EMFEmojiCategory.value(forKey: "categoryIdentifierList") as! [String]
return list
}
func categoryLocalizedName(for identifier: String) -> String {
let category = categoryWithIdentifier(identifier)
let localizedName = category.value(forKey: "localizedName") as! String
return localizedName
}
func emojisFromCategory(_ identifier: String) -> [String] {
let category = categoryWithIdentifier(identifier)
let tokens = category.perform(
NSSelectorFromString("emojiTokensForLocaleData:"), with: nil
).takeUnretainedValue() as! [NSObject]
let tokensAsStrings: [String] = tokens.compactMap {
$0.value(forKey: "string") as? String
}
return tokensAsStrings
}
func getSFSymbol(for identifier: String) -> String {
switch identifier {
case "EMFEmojiCategoryPeople": return "face.smiling"
case "EMFEmojiCategoryNature": return "teddybear"
case "EMFEmojiCategoryFoodAndDrink": return "fork.knife"
case "EMFEmojiCategoryActivity": return "basketball"
case "EMFEmojiCategoryTravelAndPlaces": return "car"
case "EMFEmojiCategoryObjects": return "lightbulb"
case "EMFEmojiCategorySymbols": return "music.note"
case "EMFEmojiCategoryFlags": return "flag"
default: return "clock"
}
}
static var recentsKey: String {
return "EMFEmojiCategoryRecents"
}
}
// Thanks to pookjw: https://github.com/pookjw/EmojiExplorer
fileprivate func categoryWithIdentifier(_ identifier: String) -> AnyObject {
let handle = dlopen("/System/Library/PrivateFrameworks/EmojiFoundation.framework/EmojiFoundation", RTLD_NOW)!
let symbol = identifier.withCString { ptr in
dlsym(handle, ptr)
}!
let identifierPtr = symbol.assumingMemoryBound(to: AnyObject.self)
let EMFEmojiCategory: AnyClass = objc_lookUpClass("EMFEmojiCategory")!
let cmd_categoryWithIdentifier = Selector(("categoryWithIdentifier:"))
let method_categoryWithIdentifier = class_getClassMethod(EMFEmojiCategory, cmd_categoryWithIdentifier)!
let imp_categoryWithIdentifier = method_getImplementation(method_categoryWithIdentifier)
let func_categoryWithIdentifier = unsafeBitCast(imp_categoryWithIdentifier, to: (@convention(c) (AnyClass, Selector, AnyObject) -> AnyObject).self)
let category = func_categoryWithIdentifier(EMFEmojiCategory, cmd_categoryWithIdentifier, identifierPtr.pointee)
return category
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment