Skip to content

Instantly share code, notes, and snippets.

@rr-codes
Last active August 1, 2020 10:56
Show Gist options
  • Select an option

  • Save rr-codes/906ea63b7fafa6603695e689cf555e49 to your computer and use it in GitHub Desktop.

Select an option

Save rr-codes/906ea63b7fafa6603695e689cf555e49 to your computer and use it in GitHub Desktop.
A SwiftUI emoji picker
//
// EmojiModel.swift
// Countdown
//
// Created by Richard Robinson on 2020-07-31.
//
import Foundation
fileprivate let emojiCategories = [
29 : "Smileys & People",
30 : "Animals & Nature",
32 : "Travel & Places",
33 : "Activity",
34 : "Objects",
36 : "Flags"
]
struct EmojiDatabase: Decodable {
let groups: [EmojiGroup]
let dictionary: [Int: EmojiGroup]
enum CodingKeys: String, CodingKey {
case groups
}
struct EmojiGroup: Decodable, Identifiable {
var id: Int { categoryID }
let emoji: [Emoji]
let categoryID: Int
var name: String { emojiCategories[categoryID]! }
struct Emoji: Decodable, Identifiable {
var id: String { unicode }
let name: String
let unicode: String
}
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.groups = try values.decode([EmojiGroup].self, forKey: .groups)
self.dictionary = groups.reduce(into: [:]) { $0[$1.id] = $1 }
}
}
class EmojiDBProvider {
static let shared = EmojiDBProvider(
from: try! String(contentsOfFile: Bundle.main.path(forResource: "emoji", ofType: "json")!)
)
private let contents: Data
lazy var database: EmojiDatabase = {
try! JSONDecoder().decode(EmojiDatabase.self, from: contents)
}()
init(from contents: String) {
self.contents = contents.data(using: .utf8)!
}
}
//
// EmojiView.swift
// Countdown
//
// Created by Richard Robinson on 2020-07-31.
//
import SwiftUI
fileprivate struct EmojiGroupView: View {
let gridItems = [GridItem](repeating: .init(.fixed(44)), count: 5)
let group: EmojiDatabase.EmojiGroup
@Binding var selectedEmoji: String
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: gridItems, spacing: 16) {
ForEach(group.emoji) { emoji in
Text(emoji.unicode)
.font(.system(size: 30))
.onTapGesture {
self.selectedEmoji = emoji.unicode
}
.background(
Circle()
.fill(Color.blue)
.opacity(self.selectedEmoji == emoji.unicode ? 0.2 : 0)
.scaleEffect(1.3)
)
.accessibility(label: Text(emoji.name))
}
}
.padding(.horizontal, 20)
}
.padding(.bottom, 13)
.padding(.top, 5)
}
}
fileprivate struct EmojiPickerView: View {
let database: EmojiDatabase
@State private var selectedCategory: Int = 29
@Binding var selectedEmoji: String
private var selectedGroup: EmojiDatabase.EmojiGroup {
database.dictionary[selectedCategory]!
}
var body: some View {
VStack(alignment: .leading) {
Text(selectedGroup.name)
.font(.caption)
.bold()
.textCase(.uppercase)
.opacity(0.5)
.padding(.leading, 20)
.padding(.top, 25)
EmojiGroupView(group: selectedGroup, selectedEmoji: $selectedEmoji)
Picker("Category", selection: $selectedCategory) {
Text("πŸ˜€").tag(29)
Text("🐢").tag(30)
Text("🌍").tag(32)
Text("πŸŽ‰").tag(33)
Text("πŸ‘”").tag(34)
Text("🏁").tag(36)
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal, 20)
.labelsHidden()
.padding(.bottom, 20)
}
.background(
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.white)
.shadow(color: Color.black.opacity(0.1), radius: 5)
)
}
}
struct EmojiOverlay: View {
let database: EmojiDatabase
@Binding var isPresented: Bool
@Binding var emoji: String
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .bottom) {
Rectangle()
.fill(Color.black)
.opacity(isPresented ? 0.1 : 0.0)
.transition(.opacity)
.zIndex(1)
if isPresented {
EmojiPickerView(database: database, selectedEmoji: $emoji)
.animation(Animation.spring().speed(2))
.transition(.move(edge: .bottom))
.padding(.horizontal, 14)
.zIndex(2)
.padding(.bottom, geometry.safeAreaInsets.bottom)
}
}
.edgesIgnoringSafeArea(.all)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment