Add support for search
This commit is contained in:
@@ -5,8 +5,7 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/tyiu/EmojiKit",
|
||||
"state" : {
|
||||
"branch" : "emoji-keywords",
|
||||
"revision" : "99744e7e8d004e1e7ebaa0e38af184ae1b6c8296"
|
||||
"revision" : "719d405244ea9ef462867c16e3d3254b7386b71f"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17,6 +16,15 @@
|
||||
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-trie",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/tyiu/swift-trie",
|
||||
"state" : {
|
||||
"revision" : "0bb65eec3d570e8a0f6bd5c6a72f10641b97c71e",
|
||||
"version" : "0.1.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
|
||||
@@ -15,14 +15,18 @@ let package = Package(
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(url: "https://github.com/tyiu/EmojiKit", branch: "emoji-keywords")
|
||||
// .package(url: "https://github.com/tyiu/EmojiKit", branch: "emoji-keywords")
|
||||
.package(url: "https://github.com/tyiu/EmojiKit", revision: "719d405244ea9ef462867c16e3d3254b7386b71f"),
|
||||
.package(url: "https://github.com/tyiu/swift-trie", .upToNextMajor(from: "0.1.1"))
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "EmojiPicker",
|
||||
dependencies: ["EmojiKit"]),
|
||||
dependencies: [
|
||||
.product(name: "EmojiKit", package: "EmojiKit"),
|
||||
.product(name: "SwiftTrie", package: "swift-trie")]),
|
||||
.testTarget(
|
||||
name: "EmojiPickerTests",
|
||||
dependencies: ["EmojiPicker"]),
|
||||
|
||||
@@ -19,14 +19,15 @@ It is a SwiftUI library that allows you to get a list of all the emojis present
|
||||
## Dependencies
|
||||
|
||||
- SwiftUI (iOS >= 15.0)
|
||||
- [Smile](https://github.com/onmyway133/Smile) (2.1.0)
|
||||
- [EmojiKit](https://github.com/tyiu/EmojiKit) (`719d405244ea9ef462867c16e3d3254b7386b71f`)
|
||||
- [SwiftTrie](https://github.com/tyiu/swift-trie) (1.1.0)
|
||||
|
||||
## How install it?
|
||||
|
||||
Nowaday we only support Swift Package Manager. You can use build-in UI tool for XCode with this search words: `EmojiPicker` or you can add it directly with this following command :
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/Kelvas09/EmojiPicker.git", from: "1.0.0")
|
||||
.package(url: "https://github.com/tyiu/EmojiPicker.git", from: "2.0.0")
|
||||
```
|
||||
|
||||
## How use it?
|
||||
@@ -154,3 +155,7 @@ NavigationView {
|
||||
## Samples
|
||||
|
||||
You can access to sample project on folder `EmojiPickerSample`
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This Swift package was forked from [Kelvas09/EmojiPicker](https://github.com/Kelvas09/EmojiPicker).
|
||||
|
||||
@@ -7,17 +7,58 @@
|
||||
|
||||
import Foundation
|
||||
import EmojiKit
|
||||
import SwiftTrie
|
||||
|
||||
public final class DefaultEmojiProvider: EmojiProvider {
|
||||
|
||||
public init() { }
|
||||
private let emojiCategoriesCache = EmojiManager.getAvailableEmojis()
|
||||
private let trie = Trie<Emoji>()
|
||||
|
||||
public func getAppleEmojiCategories() -> [EmojiKit.AppleEmojiCategory] {
|
||||
return EmojiManager.getAvailableEmojis()
|
||||
// Unicode ranges for skin tone modifiers
|
||||
private let skinToneRanges: [ClosedRange<UInt32>] = [
|
||||
0x1F3FB...0x1F3FF // Skin tone modifiers
|
||||
]
|
||||
|
||||
public init() {
|
||||
emojiCategories.forEach { category in
|
||||
category.emojis.forEach { emoji in
|
||||
let emojiValue = emoji.value.value
|
||||
|
||||
// Insert the emoji itself as a searchable string in the trie.
|
||||
let _ = trie.insert(key: emojiValue, value: emoji.value, options: [.includeNonPrefixedMatches])
|
||||
|
||||
let emojiWithoutSkinTone = removeSkinTone(emojiValue)
|
||||
if emojiWithoutSkinTone != emojiValue {
|
||||
let _ = trie.insert(key: emojiWithoutSkinTone, value: emoji.value, options: [.includeNonPrefixedMatches])
|
||||
}
|
||||
|
||||
// Insert all the localized keywords for the emoji in the trie.
|
||||
emoji.value.localizedKeywords.forEach { locale in
|
||||
locale.value.forEach { keyword in
|
||||
let _ = trie.insert(key: keyword, value: emoji.value, options: [.includeNonPrefixedMatches, .includeCaseInsensitiveMatches, .includeDiacriticsInsensitiveMatches])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func getAllEmojis() -> [EmojiKit.Emoji] {
|
||||
return EmojiManager.getAvailableEmojis().flatMap { $0.emojis.values }
|
||||
// Function to check if a scalar is a skin tone modifier
|
||||
private func isSkinToneModifier(scalar: Unicode.Scalar) -> Bool {
|
||||
return skinToneRanges.contains { $0.contains(scalar.value) }
|
||||
}
|
||||
|
||||
private func removeSkinTone(_ string: String) -> String {
|
||||
let filteredScalars = string.unicodeScalars.filter { !isSkinToneModifier(scalar: $0) }
|
||||
return String(String.UnicodeScalarView(filteredScalars))
|
||||
}
|
||||
|
||||
public var emojiCategories: [AppleEmojiCategory] {
|
||||
emojiCategoriesCache
|
||||
}
|
||||
|
||||
public func find(query: String) -> [Emoji] {
|
||||
let queryWithoutSkinTone = removeSkinTone(query)
|
||||
return trie.find(key: queryWithoutSkinTone.localizedLowercase)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import EmojiKit
|
||||
import SwiftTrie
|
||||
|
||||
public struct EmojiPickerView: View {
|
||||
|
||||
@@ -16,6 +17,9 @@ public struct EmojiPickerView: View {
|
||||
@Binding
|
||||
public var selectedEmoji: Emoji?
|
||||
|
||||
@State
|
||||
var selectedCategoryName: EmojiCategory.Name = .smileysAndPeople
|
||||
|
||||
@State
|
||||
private var search: String = ""
|
||||
|
||||
@@ -23,55 +27,131 @@ public struct EmojiPickerView: View {
|
||||
private var searchEnabled: Bool
|
||||
|
||||
private let emojiCategories: [AppleEmojiCategory]
|
||||
private let emojiProvider: EmojiProvider
|
||||
|
||||
public init(selectedEmoji: Binding<Emoji?>, searchEnabled: Bool = false, selectedColor: Color = .blue, emojiProvider: EmojiProvider = DefaultEmojiProvider(), emojiCategories: [AppleEmojiCategory] = EmojiManager.getAvailableEmojis()) {
|
||||
public init(selectedEmoji: Binding<Emoji?>, searchEnabled: Bool = false, selectedColor: Color = Color.accentColor, emojiProvider: EmojiProvider = DefaultEmojiProvider()) {
|
||||
self._selectedEmoji = selectedEmoji
|
||||
self.selectedColor = selectedColor
|
||||
self.searchEnabled = searchEnabled
|
||||
self.emojiCategories = emojiProvider.getAppleEmojiCategories()
|
||||
self.emojiProvider = emojiProvider
|
||||
self.emojiCategories = emojiProvider.emojiCategories
|
||||
}
|
||||
|
||||
let columns = [
|
||||
GridItem(.adaptive(minimum: 36))
|
||||
]
|
||||
|
||||
public var body: some View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, alignment: .leading) {
|
||||
ForEach(emojiCategories, id: \.self) { category in
|
||||
Section {
|
||||
ForEach(category.emojis.values, id: \.self) { emoji in
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill((selectedEmoji == emoji ? selectedColor : Color.clear).opacity(0.4))
|
||||
.frame(width: 36, height: 36)
|
||||
.overlay {
|
||||
Text(emoji.value)
|
||||
.font(.largeTitle)
|
||||
}
|
||||
.onTapGesture {
|
||||
selectedEmoji = emoji
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(category.name.localizedName)
|
||||
.foregroundStyle(.gray)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.frame(alignment: .leading)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
private var searchResults: [Emoji] {
|
||||
if search.isEmpty {
|
||||
return []
|
||||
} else {
|
||||
return emojiProvider.find(query: search)
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ScrollViewReader { proxy in
|
||||
VStack {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, alignment: .leading) {
|
||||
if !searchEnabled || search.isEmpty {
|
||||
ForEach(emojiCategories, id: \.self) { category in
|
||||
Section {
|
||||
ForEach(category.emojis.values, id: \.self) { emoji in
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill((selectedEmoji == emoji ? selectedColor : Color.clear).opacity(0.4))
|
||||
.frame(width: 36, height: 36)
|
||||
.overlay {
|
||||
Text(emoji.value)
|
||||
.font(.largeTitle)
|
||||
}
|
||||
.onTapGesture {
|
||||
selectedEmoji = emoji
|
||||
dismiss()
|
||||
}
|
||||
.onAppear {
|
||||
self.selectedCategoryName = category.name
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(category.name.localizedName)
|
||||
.foregroundStyle(.gray)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.frame(alignment: .leading)
|
||||
.id(category.name)
|
||||
.onAppear {
|
||||
self.selectedCategoryName = category.name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ForEach(searchResults, id: \.self) { emoji in
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill((selectedEmoji == emoji ? selectedColor : Color.clear).opacity(0.4))
|
||||
.frame(width: 36, height: 36)
|
||||
.overlay {
|
||||
Text(emoji.value)
|
||||
.font(.largeTitle)
|
||||
}
|
||||
.onTapGesture {
|
||||
selectedEmoji = emoji
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.searchable(text: $search, placement: .navigationBarDrawer(displayMode: .always))
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
|
||||
if search.isEmpty {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(EmojiCategory.Name.orderedCases, id: \.self) { emojiCategoryName in
|
||||
Image(systemName: emojiCategoryName.imageName)
|
||||
.font(.system(size: 18))
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(selectedCategoryName == emojiCategoryName ? Color.accentColor : .secondary)
|
||||
.onTapGesture {
|
||||
selectedCategoryName = emojiCategoryName
|
||||
proxy.scrollTo(emojiCategoryName, anchor: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AppleEmojiCategory.Name {
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .smileysAndPeople:
|
||||
return "face.smiling"
|
||||
case .animalsAndNature:
|
||||
return "teddybear"
|
||||
case .foodAndDrink:
|
||||
return "fork.knife"
|
||||
case .activity:
|
||||
return "basketball"
|
||||
case .travelAndPlaces:
|
||||
return "car"
|
||||
case .objects:
|
||||
return "lightbulb"
|
||||
case .symbols:
|
||||
return "music.note"
|
||||
case .flags:
|
||||
return "flag"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmojiPickerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EmojiPickerView(selectedEmoji: .constant(Emoji(value: "", keywords: [])))
|
||||
EmojiPickerView(selectedEmoji: .constant(Emoji(value: "", localizedKeywords: [:])), searchEnabled: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ import Foundation
|
||||
import EmojiKit
|
||||
|
||||
public protocol EmojiProvider {
|
||||
func getAppleEmojiCategories() -> [AppleEmojiCategory]
|
||||
func getAllEmojis() -> [Emoji]
|
||||
var emojiCategories: [AppleEmojiCategory] { get }
|
||||
func find(query: String) -> [Emoji]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user