Add support for search
This commit is contained in:
@@ -5,8 +5,7 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/tyiu/EmojiKit",
|
"location" : "https://github.com/tyiu/EmojiKit",
|
||||||
"state" : {
|
"state" : {
|
||||||
"branch" : "emoji-keywords",
|
"revision" : "719d405244ea9ef462867c16e3d3254b7386b71f"
|
||||||
"revision" : "99744e7e8d004e1e7ebaa0e38af184ae1b6c8296"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -17,6 +16,15 @@
|
|||||||
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
|
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
|
||||||
"version" : "1.1.1"
|
"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
|
"version" : 2
|
||||||
|
|||||||
@@ -15,14 +15,18 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
// Dependencies declare other packages that this package depends on.
|
// Dependencies declare other packages that this package depends on.
|
||||||
// .package(url: /* package url */, from: "1.0.0"),
|
// .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: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// 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.
|
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||||
.target(
|
.target(
|
||||||
name: "EmojiPicker",
|
name: "EmojiPicker",
|
||||||
dependencies: ["EmojiKit"]),
|
dependencies: [
|
||||||
|
.product(name: "EmojiKit", package: "EmojiKit"),
|
||||||
|
.product(name: "SwiftTrie", package: "swift-trie")]),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "EmojiPickerTests",
|
name: "EmojiPickerTests",
|
||||||
dependencies: ["EmojiPicker"]),
|
dependencies: ["EmojiPicker"]),
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ It is a SwiftUI library that allows you to get a list of all the emojis present
|
|||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- SwiftUI (iOS >= 15.0)
|
- 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?
|
## 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 :
|
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
|
```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?
|
## How use it?
|
||||||
@@ -154,3 +155,7 @@ NavigationView {
|
|||||||
## Samples
|
## Samples
|
||||||
|
|
||||||
You can access to sample project on folder `EmojiPickerSample`
|
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 Foundation
|
||||||
import EmojiKit
|
import EmojiKit
|
||||||
|
import SwiftTrie
|
||||||
|
|
||||||
public final class DefaultEmojiProvider: EmojiProvider {
|
public final class DefaultEmojiProvider: EmojiProvider {
|
||||||
|
|
||||||
public init() { }
|
private let emojiCategoriesCache = EmojiManager.getAvailableEmojis()
|
||||||
|
private let trie = Trie<Emoji>()
|
||||||
|
|
||||||
public func getAppleEmojiCategories() -> [EmojiKit.AppleEmojiCategory] {
|
// Unicode ranges for skin tone modifiers
|
||||||
return EmojiManager.getAvailableEmojis()
|
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] {
|
// Function to check if a scalar is a skin tone modifier
|
||||||
return EmojiManager.getAvailableEmojis().flatMap { $0.emojis.values }
|
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 SwiftUI
|
||||||
import EmojiKit
|
import EmojiKit
|
||||||
|
import SwiftTrie
|
||||||
|
|
||||||
public struct EmojiPickerView: View {
|
public struct EmojiPickerView: View {
|
||||||
|
|
||||||
@@ -16,6 +17,9 @@ public struct EmojiPickerView: View {
|
|||||||
@Binding
|
@Binding
|
||||||
public var selectedEmoji: Emoji?
|
public var selectedEmoji: Emoji?
|
||||||
|
|
||||||
|
@State
|
||||||
|
var selectedCategoryName: EmojiCategory.Name = .smileysAndPeople
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var search: String = ""
|
private var search: String = ""
|
||||||
|
|
||||||
@@ -23,55 +27,131 @@ public struct EmojiPickerView: View {
|
|||||||
private var searchEnabled: Bool
|
private var searchEnabled: Bool
|
||||||
|
|
||||||
private let emojiCategories: [AppleEmojiCategory]
|
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._selectedEmoji = selectedEmoji
|
||||||
self.selectedColor = selectedColor
|
self.selectedColor = selectedColor
|
||||||
self.searchEnabled = searchEnabled
|
self.searchEnabled = searchEnabled
|
||||||
self.emojiCategories = emojiProvider.getAppleEmojiCategories()
|
self.emojiProvider = emojiProvider
|
||||||
|
self.emojiCategories = emojiProvider.emojiCategories
|
||||||
}
|
}
|
||||||
|
|
||||||
let columns = [
|
let columns = [
|
||||||
GridItem(.adaptive(minimum: 36))
|
GridItem(.adaptive(minimum: 36))
|
||||||
]
|
]
|
||||||
|
|
||||||
public var body: some View {
|
private var searchResults: [Emoji] {
|
||||||
ScrollView {
|
if search.isEmpty {
|
||||||
LazyVGrid(columns: columns, alignment: .leading) {
|
return []
|
||||||
ForEach(emojiCategories, id: \.self) { category in
|
} else {
|
||||||
Section {
|
return emojiProvider.find(query: search)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
.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 {
|
struct EmojiPickerView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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
|
import EmojiKit
|
||||||
|
|
||||||
public protocol EmojiProvider {
|
public protocol EmojiProvider {
|
||||||
func getAppleEmojiCategories() -> [AppleEmojiCategory]
|
var emojiCategories: [AppleEmojiCategory] { get }
|
||||||
func getAllEmojis() -> [Emoji]
|
func find(query: String) -> [Emoji]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user