├── .github └── workflows │ └── swift.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── EmojiPicker.podspec ├── Example App ├── EmojiPicker.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── EmojiPicker-Example.xcscheme │ └── xcuserdata │ │ └── egbad.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── EmojiPicker.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── egbad.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── EmojiPicker │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift ├── Podfile ├── Podfile.lock └── Pods │ ├── Local Podspecs │ └── EmojiPicker.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── egbad.xcuserdatad │ │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── egbad.xcuserdatad │ │ └── xcschemes │ │ ├── EmojiPicker-Resources.xcscheme │ │ ├── EmojiPicker-Unit-Tests.xcscheme │ │ ├── EmojiPicker.xcscheme │ │ ├── Pods-EmojiPicker_Example.xcscheme │ │ └── xcschememanagement.plist │ └── Target Support Files │ ├── EmojiPicker │ ├── EmojiPicker-Info.plist │ ├── EmojiPicker-Unit-Tests-Info.plist │ ├── EmojiPicker-Unit-Tests-frameworks.sh │ ├── EmojiPicker-Unit-Tests-prefix.pch │ ├── EmojiPicker-Unit-Tests-resources.sh │ ├── EmojiPicker-dummy.m │ ├── EmojiPicker-prefix.pch │ ├── EmojiPicker-umbrella.h │ ├── EmojiPicker.debug.xcconfig │ ├── EmojiPicker.modulemap │ ├── EmojiPicker.release.xcconfig │ ├── EmojiPicker.unit-tests.debug.xcconfig │ ├── EmojiPicker.unit-tests.release.xcconfig │ ├── ResourceBundle-EmojiPicker-EmojiPicker-Info.plist │ └── ResourceBundle-Resources-EmojiPicker-Info.plist │ └── Pods-EmojiPicker_Example │ ├── Pods-EmojiPicker_Example-Info.plist │ ├── Pods-EmojiPicker_Example-acknowledgements.markdown │ ├── Pods-EmojiPicker_Example-acknowledgements.plist │ ├── Pods-EmojiPicker_Example-dummy.m │ ├── Pods-EmojiPicker_Example-frameworks.sh │ ├── Pods-EmojiPicker_Example-umbrella.h │ ├── Pods-EmojiPicker_Example.debug.xcconfig │ ├── Pods-EmojiPicker_Example.modulemap │ └── Pods-EmojiPicker_Example.release.xcconfig ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── EmojiPicker │ ├── Bindings │ └── Observable.swift │ ├── Extensions │ ├── Foundation │ │ └── Bundle.swift │ └── UIKit │ │ ├── UIColor.swift │ │ └── UIView.swift │ ├── Models │ ├── Category.swift │ ├── Emoji.swift │ ├── EmojiSet.swift │ ├── PickerArrowDirectionMode.swift │ └── Skin.swift │ ├── Resources │ ├── Data │ │ ├── 11.json │ │ ├── 12.1.json │ │ ├── 13.1.json │ │ ├── 13.json │ │ ├── 14.json │ │ └── 5.json │ └── Localization │ │ ├── de.lproj │ │ └── Localizable.strings │ │ ├── en.lproj │ │ └── Localizable.strings │ │ ├── fr.lproj │ │ └── Localizable.strings │ │ ├── hi.lproj │ │ └── Localizable.strings │ │ ├── ru.lproj │ │ └── Localizable.strings │ │ ├── tr.lproj │ │ └── Localizable.strings │ │ ├── uk.lproj │ │ └── Localizable.strings │ │ └── zh.lproj │ │ └── Localizable.strings │ ├── Services │ └── EmojiManager.swift │ ├── ViewModel │ └── EmojiPickerViewModel.swift │ └── Views │ ├── EmojiCategoryView │ ├── EmojiCategoryIconView.swift │ └── TouchableEmojiCategoryView.swift │ ├── EmojiCollectionViewCell.swift │ ├── EmojiCollectionViewHeader.swift │ ├── EmojiPickerView.swift │ └── EmojiPickerViewController.swift ├── TODO.md └── Tests └── EmojiPickerTests ├── CategoryTests.swift ├── EmojiManagerTests.swift ├── EmojiPickerViewModelTests.swift ├── Mocks └── EmojiPickerDelegateMock.swift ├── ObservableTests.swift ├── PickerArrowDirectionModeTests.swift └── Stubs └── EmojiManagerStub.swift /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | pull_request: 9 | branches: 10 | - main 11 | - develop 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: macos-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Build on iOS 16.2 21 | run: xcodebuild -workspace Example\ App/EmojiPicker.xcworkspace -scheme EmojiPicker -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=16.2,name=iPhone 14' 22 | - name: Test on iOS 16.2 23 | run: xcodebuild -workspace Example\ App/EmojiPicker.xcworkspace -scheme EmojiPicker-Example -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=16.2,name=iPhone 14' test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Development 2 | .swiftpm/ 3 | .build/ 4 | 5 | # macOS files 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | badmaeve2511@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Here provided more info about project, contribution process and recomended changes. 4 | Please, read it before pull request or create issue. 5 | 6 | ## Codestyle 7 | 8 | ### Marks 9 | 10 | For clean struct code good is using marks. 11 | 12 | ```swift 13 | class Example { 14 | 15 | // MARK: - Init 16 | 17 | init() {} 18 | } 19 | ``` 20 | 21 | Here you find all which using in project: 22 | 23 | - // MARK: - Init 24 | - // MARK: - Life Cycle 25 | - // MARK: - Public Properties 26 | - // MARK: - Public Methods 27 | - // MARK: - Private Properties 28 | - // MARK: - Private Methods 29 | - // MARK: - Ovveride 30 | - // MARK: - Layout 31 | 32 | If you can't find valid, add new to codestyle agreements please. Other can be use if class is large and need struct it even without adding to codestyle agreements. 33 | 34 | ## Conventional Commits 35 | 36 | - Commit names should be according to the [guideline](https://github.com/htmlprogrammist/EmojiPicker/blob/main/CONTRIBUTING.md#guideline) 37 | - The present tense should be used ("add function", not "added function") 38 | - The imperative mood should be used ("move the cursor to...", not "move the cursor to...") 39 | 40 | ### Guideline 41 | 42 | - `init:` - used to start a project or task. Examples: 43 | 44 | ``` 45 | init: start youtube-task 46 | init: start mentor-dashboard task 47 | ``` 48 | 49 | - `feat:` - describes implementing new functionality from the terms of reference (added zoom support, added footer, added a product card). Examples: 50 | 51 | ``` 52 | feat: add basic page layout 53 | feat: implement search box 54 | feat: implement request to youtube API 55 | feat: implement swipe for horizontal list 56 | feat: add additional navigation button 57 | feat: add banner 58 | feat: add social links 59 | feat: add physical security section 60 | feat: add real social icons 61 | ``` 62 | 63 | - `fix:` - describes fixing a bug in previously implemented functionality. Examples: 64 | 65 | ``` 66 | fix: implement correct loading data from youtube 67 | fix: change layout for video items to fix bugs 68 | fix: relayout header for firefox 69 | fix: adjust social links for mobile 70 | ``` 71 | 72 | - `refactor:` - I didn't add any new functionality/didn't change the behavior. I put the files in other places, deleted them, added them. Changed the formatting of the code (white-space, formatting, missing semi-columns, etc). Improved the algorithm, without changing the functionality. Examples: 73 | 74 | ``` 75 | refactor: change structure of the project 76 | refactor: rename vars for better readability 77 | refactor: apply eslint 78 | refactor: apply prettier 79 | ``` 80 | 81 | - `docs:` - used when working with the documentation/README of the project. Examples: 82 | 83 | ``` 84 | docs: update readme with additional information 85 | docs: update description of run() method 86 | ``` 87 | 88 | ## Before making pull request 89 | 90 | 1. Change version in [`EmojiPicker.podspec`](/EmojiPicker.podspec) file 91 | 2. Update version of dependency in Example App: 92 | 1. Open terminal 93 | 2. Open Example app directory using `cd Example\ App/` 94 | 3. Update pod version via command `pod install` 95 | -------------------------------------------------------------------------------- /EmojiPicker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'EmojiPicker' 3 | s.version = '3.0.9' 4 | s.license = 'MIT' 5 | s.summary = 'Emoji picker for iOS like on MacOS' 6 | s.homepage = 'https://github.com/htmlprogrammist/EmojiPicker' 7 | s.authors = { 'Egor Badmaev' => 'eg.badmaev@gmail.com' } 8 | 9 | s.source = { :git => 'https://github.com/htmlprogrammist/EmojiPicker.git', :tag => s.version.to_s } 10 | s.source_files = 'Sources/EmojiPicker/**/*.{swift}' 11 | s.resource_bundle = { "Resources" => ["Sources/EmojiPicker/**/*.{json,strings}"] } 12 | 13 | s.test_spec 'Tests' do |test_spec| 14 | test_spec.source_files = 'Tests/EmojiPickerTests/**/*.{swift}' 15 | end 16 | 17 | s.swift_version = '4.2' 18 | s.platform = :ios, '11.1' 19 | end 20 | -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 11 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 12 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 13 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 14 | E4436A55215F9BB302740724 /* Pods_EmojiPicker_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52C75966261C87A105AE3101 /* Pods_EmojiPicker_Example.framework */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 028AB9FF7F7146CA02C6047E /* Pods-EmojiPicker_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EmojiPicker_Example.debug.xcconfig"; path = "Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example.debug.xcconfig"; sourceTree = ""; }; 19 | 39C5556144E64B4781AC3E2C /* EmojiPicker.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = EmojiPicker.podspec; path = ../EmojiPicker.podspec; sourceTree = ""; }; 20 | 4E7BA306C8344910609AD79B /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 21 | 52C75966261C87A105AE3101 /* Pods_EmojiPicker_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EmojiPicker_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 607FACD01AFB9204008FA782 /* EmojiPicker_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EmojiPicker_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 26 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 27 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 28 | 65D929EDFC50784D9EB46DCA /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 29 | B614CCEF28D73979E7E5205C /* Pods-EmojiPicker_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EmojiPicker_Example.release.xcconfig"; path = "Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example.release.xcconfig"; sourceTree = ""; }; 30 | D5032E13F2F318B80621E315 /* Pods_EmojiPicker_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EmojiPicker_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | D96029D6DE984689110611ED /* Pods-EmojiPicker_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EmojiPicker_Tests.release.xcconfig"; path = "Target Support Files/Pods-EmojiPicker_Tests/Pods-EmojiPicker_Tests.release.xcconfig"; sourceTree = ""; }; 32 | E2EE0C28865D54C234AAC257 /* Pods-EmojiPicker_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EmojiPicker_Tests.debug.xcconfig"; path = "Target Support Files/Pods-EmojiPicker_Tests/Pods-EmojiPicker_Tests.debug.xcconfig"; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | E4436A55215F9BB302740724 /* Pods_EmojiPicker_Example.framework in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 607FACC71AFB9204008FA782 = { 48 | isa = PBXGroup; 49 | children = ( 50 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 51 | 607FACD21AFB9204008FA782 /* Example for EmojiPicker */, 52 | 607FACD11AFB9204008FA782 /* Products */, 53 | 6476F6CB8B1FAC87EF38A257 /* Pods */, 54 | F67B71FFE5ADD93E0EB41412 /* Frameworks */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 607FACD11AFB9204008FA782 /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 607FACD01AFB9204008FA782 /* EmojiPicker_Example.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | 607FACD21AFB9204008FA782 /* Example for EmojiPicker */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 70 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 71 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 72 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 73 | 607FACD31AFB9204008FA782 /* Supporting Files */, 74 | ); 75 | name = "Example for EmojiPicker"; 76 | path = EmojiPicker; 77 | sourceTree = ""; 78 | }; 79 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 607FACD41AFB9204008FA782 /* Info.plist */, 83 | ); 84 | name = "Supporting Files"; 85 | sourceTree = ""; 86 | }; 87 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 39C5556144E64B4781AC3E2C /* EmojiPicker.podspec */, 91 | 65D929EDFC50784D9EB46DCA /* README.md */, 92 | 4E7BA306C8344910609AD79B /* LICENSE */, 93 | ); 94 | name = "Podspec Metadata"; 95 | sourceTree = ""; 96 | }; 97 | 6476F6CB8B1FAC87EF38A257 /* Pods */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 028AB9FF7F7146CA02C6047E /* Pods-EmojiPicker_Example.debug.xcconfig */, 101 | B614CCEF28D73979E7E5205C /* Pods-EmojiPicker_Example.release.xcconfig */, 102 | E2EE0C28865D54C234AAC257 /* Pods-EmojiPicker_Tests.debug.xcconfig */, 103 | D96029D6DE984689110611ED /* Pods-EmojiPicker_Tests.release.xcconfig */, 104 | ); 105 | path = Pods; 106 | sourceTree = ""; 107 | }; 108 | F67B71FFE5ADD93E0EB41412 /* Frameworks */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 52C75966261C87A105AE3101 /* Pods_EmojiPicker_Example.framework */, 112 | D5032E13F2F318B80621E315 /* Pods_EmojiPicker_Tests.framework */, 113 | ); 114 | name = Frameworks; 115 | sourceTree = ""; 116 | }; 117 | /* End PBXGroup section */ 118 | 119 | /* Begin PBXNativeTarget section */ 120 | 607FACCF1AFB9204008FA782 /* EmojiPicker_Example */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EmojiPicker_Example" */; 123 | buildPhases = ( 124 | 1F729D6E9358157E974194CF /* [CP] Check Pods Manifest.lock */, 125 | 607FACCC1AFB9204008FA782 /* Sources */, 126 | 607FACCD1AFB9204008FA782 /* Frameworks */, 127 | 607FACCE1AFB9204008FA782 /* Resources */, 128 | CDCD5DB1FA1C6883C3FBA2A6 /* [CP] Embed Pods Frameworks */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = EmojiPicker_Example; 135 | productName = EmojiPicker; 136 | productReference = 607FACD01AFB9204008FA782 /* EmojiPicker_Example.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | 607FACC81AFB9204008FA782 /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | LastSwiftUpdateCheck = 0830; 146 | LastUpgradeCheck = 0830; 147 | ORGANIZATIONNAME = CocoaPods; 148 | TargetAttributes = { 149 | 607FACCF1AFB9204008FA782 = { 150 | CreatedOnToolsVersion = 6.3.1; 151 | LastSwiftMigration = 1340; 152 | }; 153 | }; 154 | }; 155 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "EmojiPicker" */; 156 | compatibilityVersion = "Xcode 3.2"; 157 | developmentRegion = English; 158 | hasScannedForEncodings = 0; 159 | knownRegions = ( 160 | English, 161 | en, 162 | Base, 163 | ); 164 | mainGroup = 607FACC71AFB9204008FA782; 165 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 166 | projectDirPath = ""; 167 | projectRoot = ""; 168 | targets = ( 169 | 607FACCF1AFB9204008FA782 /* EmojiPicker_Example */, 170 | ); 171 | }; 172 | /* End PBXProject section */ 173 | 174 | /* Begin PBXResourcesBuildPhase section */ 175 | 607FACCE1AFB9204008FA782 /* Resources */ = { 176 | isa = PBXResourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 180 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXResourcesBuildPhase section */ 185 | 186 | /* Begin PBXShellScriptBuildPhase section */ 187 | 1F729D6E9358157E974194CF /* [CP] Check Pods Manifest.lock */ = { 188 | isa = PBXShellScriptBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | ); 192 | inputFileListPaths = ( 193 | ); 194 | inputPaths = ( 195 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 196 | "${PODS_ROOT}/Manifest.lock", 197 | ); 198 | name = "[CP] Check Pods Manifest.lock"; 199 | outputFileListPaths = ( 200 | ); 201 | outputPaths = ( 202 | "$(DERIVED_FILE_DIR)/Pods-EmojiPicker_Example-checkManifestLockResult.txt", 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 207 | showEnvVarsInLog = 0; 208 | }; 209 | CDCD5DB1FA1C6883C3FBA2A6 /* [CP] Embed Pods Frameworks */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputPaths = ( 215 | "${PODS_ROOT}/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-frameworks.sh", 216 | "${BUILT_PRODUCTS_DIR}/EmojiPicker/EmojiPicker.framework", 217 | ); 218 | name = "[CP] Embed Pods Frameworks"; 219 | outputPaths = ( 220 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EmojiPicker.framework", 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | shellPath = /bin/sh; 224 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-frameworks.sh\"\n"; 225 | showEnvVarsInLog = 0; 226 | }; 227 | /* End PBXShellScriptBuildPhase section */ 228 | 229 | /* Begin PBXSourcesBuildPhase section */ 230 | 607FACCC1AFB9204008FA782 /* Sources */ = { 231 | isa = PBXSourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 235 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXSourcesBuildPhase section */ 240 | 241 | /* Begin PBXVariantGroup section */ 242 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 243 | isa = PBXVariantGroup; 244 | children = ( 245 | 607FACDF1AFB9204008FA782 /* Base */, 246 | ); 247 | name = LaunchScreen.xib; 248 | sourceTree = ""; 249 | }; 250 | /* End PBXVariantGroup section */ 251 | 252 | /* Begin XCBuildConfiguration section */ 253 | 607FACED1AFB9204008FA782 /* Debug */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_COMMA = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 274 | CLANG_WARN_STRICT_PROTOTYPES = YES; 275 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 279 | COPY_PHASE_STRIP = NO; 280 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 281 | ENABLE_STRICT_OBJC_MSGSEND = YES; 282 | ENABLE_TESTABILITY = YES; 283 | GCC_C_LANGUAGE_STANDARD = gnu99; 284 | GCC_DYNAMIC_NO_PIC = NO; 285 | GCC_NO_COMMON_BLOCKS = YES; 286 | GCC_OPTIMIZATION_LEVEL = 0; 287 | GCC_PREPROCESSOR_DEFINITIONS = ( 288 | "DEBUG=1", 289 | "$(inherited)", 290 | ); 291 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 299 | MTL_ENABLE_DEBUG_INFO = YES; 300 | ONLY_ACTIVE_ARCH = YES; 301 | SDKROOT = iphoneos; 302 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 303 | }; 304 | name = Debug; 305 | }; 306 | 607FACEE1AFB9204008FA782 /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 319 | CLANG_WARN_EMPTY_BODY = YES; 320 | CLANG_WARN_ENUM_CONVERSION = YES; 321 | CLANG_WARN_INFINITE_RECURSION = YES; 322 | CLANG_WARN_INT_CONVERSION = YES; 323 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 326 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 327 | CLANG_WARN_STRICT_PROTOTYPES = YES; 328 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 329 | CLANG_WARN_UNREACHABLE_CODE = YES; 330 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 331 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 332 | COPY_PHASE_STRIP = NO; 333 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 334 | ENABLE_NS_ASSERTIONS = NO; 335 | ENABLE_STRICT_OBJC_MSGSEND = YES; 336 | GCC_C_LANGUAGE_STANDARD = gnu99; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 339 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 340 | GCC_WARN_UNDECLARED_SELECTOR = YES; 341 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 342 | GCC_WARN_UNUSED_FUNCTION = YES; 343 | GCC_WARN_UNUSED_VARIABLE = YES; 344 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 345 | MTL_ENABLE_DEBUG_INFO = NO; 346 | SDKROOT = iphoneos; 347 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 348 | VALIDATE_PRODUCT = YES; 349 | }; 350 | name = Release; 351 | }; 352 | 607FACF01AFB9204008FA782 /* Debug */ = { 353 | isa = XCBuildConfiguration; 354 | baseConfigurationReference = 028AB9FF7F7146CA02C6047E /* Pods-EmojiPicker_Example.debug.xcconfig */; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | INFOPLIST_FILE = EmojiPicker/Info.plist; 358 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 359 | MODULE_NAME = ExampleApp; 360 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 361 | PRODUCT_NAME = "$(TARGET_NAME)"; 362 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 363 | SUPPORTS_MACCATALYST = NO; 364 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 365 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 366 | SWIFT_VERSION = 4.0; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | }; 369 | name = Debug; 370 | }; 371 | 607FACF11AFB9204008FA782 /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | baseConfigurationReference = B614CCEF28D73979E7E5205C /* Pods-EmojiPicker_Example.release.xcconfig */; 374 | buildSettings = { 375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 376 | INFOPLIST_FILE = EmojiPicker/Info.plist; 377 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 378 | MODULE_NAME = ExampleApp; 379 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 380 | PRODUCT_NAME = "$(TARGET_NAME)"; 381 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 382 | SUPPORTS_MACCATALYST = NO; 383 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 384 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 385 | SWIFT_VERSION = 4.0; 386 | TARGETED_DEVICE_FAMILY = "1,2"; 387 | }; 388 | name = Release; 389 | }; 390 | /* End XCBuildConfiguration section */ 391 | 392 | /* Begin XCConfigurationList section */ 393 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "EmojiPicker" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | 607FACED1AFB9204008FA782 /* Debug */, 397 | 607FACEE1AFB9204008FA782 /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EmojiPicker_Example" */ = { 403 | isa = XCConfigurationList; 404 | buildConfigurations = ( 405 | 607FACF01AFB9204008FA782 /* Debug */, 406 | 607FACF11AFB9204008FA782 /* Release */, 407 | ); 408 | defaultConfigurationIsVisible = 0; 409 | defaultConfigurationName = Release; 410 | }; 411 | /* End XCConfigurationList section */ 412 | }; 413 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 414 | } 415 | -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcodeproj/xcshareddata/xcschemes/EmojiPicker-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcodeproj/xcuserdata/egbad.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | EmojiPicker-Example.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 3 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 607FACCF1AFB9204008FA782 16 | 17 | primary 18 | 19 | 20 | 607FACE41AFB9204008FA782 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcworkspace/xcuserdata/egbad.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmlprogrammist/EmojiPicker/f6cf639f1092b80a9969591e27474cb38810379d/Example App/EmojiPicker.xcworkspace/xcuserdata/egbad.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example App/EmojiPicker.xcworkspace/xcuserdata/egbad.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Example App/EmojiPicker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EmojiPicker 4 | // 5 | // Created by Егор Бадмаев on 14.01.2023. 6 | // Copyright (c) 2023 Егор Бадмаев. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | 18 | let window = UIWindow(frame: UIScreen.main.bounds) 19 | window.rootViewController = ViewController() 20 | window.makeKeyAndVisible() 21 | self.window = window 22 | 23 | return true 24 | } 25 | 26 | func applicationWillResignActive(_ application: UIApplication) { 27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 28 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 29 | } 30 | 31 | func applicationDidEnterBackground(_ application: UIApplication) { 32 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | func applicationWillEnterForeground(_ application: UIApplication) { 37 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | func applicationDidBecomeActive(_ application: UIApplication) { 41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 42 | } 43 | 44 | func applicationWillTerminate(_ application: UIApplication) { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Example App/EmojiPicker/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example App/EmojiPicker/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example App/EmojiPicker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationPortraitUpsideDown 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example App/EmojiPicker/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // EmojiPicker 4 | // 5 | // Created by Егор Бадмаев on 14.01.2023. 6 | // Copyright (c) 2023 Егор Бадмаев. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EmojiPicker 11 | 12 | class ViewController: UIViewController { 13 | 14 | private lazy var emojiButton: UIButton = { 15 | let button = UIButton() 16 | button.setTitle("😃", for: .normal) 17 | button.titleLabel?.font = UIFont.systemFont(ofSize: 70) 18 | button.addTarget(self, action: #selector(openEmojiPickerModule), for: .touchUpInside) 19 | button.translatesAutoresizingMaskIntoConstraints = false 20 | return button 21 | }() 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | setupView() 27 | } 28 | 29 | @objc private func openEmojiPickerModule(sender: UIButton) { 30 | let viewController = EmojiPickerViewController() 31 | viewController.delegate = self 32 | viewController.sourceView = sender 33 | present(viewController, animated: true) 34 | } 35 | 36 | private func setupView() { 37 | if #available(iOS 13.0, *) { 38 | view.backgroundColor = .systemBackground 39 | } else { 40 | view.backgroundColor = .white 41 | } 42 | 43 | view.addSubview(emojiButton) 44 | 45 | NSLayoutConstraint.activate([ 46 | emojiButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), 47 | emojiButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 60), 48 | emojiButton.heightAnchor.constraint(equalToConstant: 80), 49 | emojiButton.widthAnchor.constraint(equalToConstant: 80) 50 | ]) 51 | } 52 | } 53 | 54 | // MARK: - EmojiPickerDelegate 55 | 56 | extension ViewController: EmojiPickerDelegate { 57 | func didGetEmoji(emoji: String) { 58 | emojiButton.setTitle(emoji, for: .normal) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Example App/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '11.1' 4 | 5 | target 'EmojiPicker_Example' do 6 | pod 'EmojiPicker', :path => '../', :testspecs => ['Tests'] 7 | end 8 | -------------------------------------------------------------------------------- /Example App/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - EmojiPicker (3.0.9) 3 | - EmojiPicker/Tests (3.0.9) 4 | 5 | DEPENDENCIES: 6 | - EmojiPicker (from `../`) 7 | - EmojiPicker/Tests (from `../`) 8 | 9 | EXTERNAL SOURCES: 10 | EmojiPicker: 11 | :path: "../" 12 | 13 | SPEC CHECKSUMS: 14 | EmojiPicker: 67fa5a3cf1b32885248388087cc5ebeebc7de647 15 | 16 | PODFILE CHECKSUM: e686a8fbd8a5367eac0bf7fb77e0bbddf18a2353 17 | 18 | COCOAPODS: 1.11.3 19 | -------------------------------------------------------------------------------- /Example App/Pods/Local Podspecs/EmojiPicker.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EmojiPicker", 3 | "version": "3.0.9", 4 | "license": "MIT", 5 | "summary": "Emoji picker for iOS like on MacOS", 6 | "homepage": "https://github.com/htmlprogrammist/EmojiPicker", 7 | "authors": { 8 | "Egor Badmaev": "eg.badmaev@gmail.com" 9 | }, 10 | "source": { 11 | "git": "https://github.com/htmlprogrammist/EmojiPicker.git", 12 | "tag": "3.0.9" 13 | }, 14 | "source_files": "Sources/EmojiPicker/**/*.{swift}", 15 | "resource_bundles": { 16 | "Resources": [ 17 | "Sources/EmojiPicker/**/*.{json,strings}" 18 | ] 19 | }, 20 | "swift_versions": "4.2", 21 | "platforms": { 22 | "ios": "11.1" 23 | }, 24 | "testspecs": [ 25 | { 26 | "name": "Tests", 27 | "test_type": "unit", 28 | "source_files": "Tests/EmojiPickerTests/**/*.{swift}" 29 | } 30 | ], 31 | "swift_version": "4.2" 32 | } 33 | -------------------------------------------------------------------------------- /Example App/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - EmojiPicker (3.0.9) 3 | - EmojiPicker/Tests (3.0.9) 4 | 5 | DEPENDENCIES: 6 | - EmojiPicker (from `../`) 7 | - EmojiPicker/Tests (from `../`) 8 | 9 | EXTERNAL SOURCES: 10 | EmojiPicker: 11 | :path: "../" 12 | 13 | SPEC CHECKSUMS: 14 | EmojiPicker: 67fa5a3cf1b32885248388087cc5ebeebc7de647 15 | 16 | PODFILE CHECKSUM: e686a8fbd8a5367eac0bf7fb77e0bbddf18a2353 17 | 18 | COCOAPODS: 1.11.3 19 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/project.xcworkspace/xcuserdata/egbad.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseTargetSettings 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/xcuserdata/egbad.xcuserdatad/xcschemes/EmojiPicker-Resources.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/xcuserdata/egbad.xcuserdatad/xcschemes/EmojiPicker-Unit-Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/xcuserdata/egbad.xcuserdatad/xcschemes/EmojiPicker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/xcuserdata/egbad.xcuserdatad/xcschemes/Pods-EmojiPicker_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example App/Pods/Pods.xcodeproj/xcuserdata/egbad.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | EmojiPicker-Resources.xcscheme 8 | 9 | isShown 10 | 11 | 12 | EmojiPicker-Unit-Tests.xcscheme 13 | 14 | isShown 15 | 16 | 17 | EmojiPicker.xcscheme 18 | 19 | isShown 20 | 21 | 22 | Pods-EmojiPicker_Example.xcscheme 23 | 24 | isShown 25 | 26 | 27 | 28 | SuppressBuildableAutocreation 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.0.9 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-Unit-Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-Unit-Tests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/EmojiPicker/EmojiPicker.framework" 180 | fi 181 | if [[ "$CONFIGURATION" == "Release" ]]; then 182 | install_framework "${BUILT_PRODUCTS_DIR}/EmojiPicker/EmojiPicker.framework" 183 | fi 184 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 185 | wait 186 | fi 187 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-Unit-Tests-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-Unit-Tests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then 12 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # resources to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 18 | 19 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 20 | > "$RESOURCES_TO_COPY" 21 | 22 | XCASSET_FILES=() 23 | 24 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 25 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 26 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 27 | 28 | case "${TARGETED_DEVICE_FAMILY:-}" in 29 | 1,2) 30 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 31 | ;; 32 | 1) 33 | TARGET_DEVICE_ARGS="--target-device iphone" 34 | ;; 35 | 2) 36 | TARGET_DEVICE_ARGS="--target-device ipad" 37 | ;; 38 | 3) 39 | TARGET_DEVICE_ARGS="--target-device tv" 40 | ;; 41 | 4) 42 | TARGET_DEVICE_ARGS="--target-device watch" 43 | ;; 44 | *) 45 | TARGET_DEVICE_ARGS="--target-device mac" 46 | ;; 47 | esac 48 | 49 | install_resource() 50 | { 51 | if [[ "$1" = /* ]] ; then 52 | RESOURCE_PATH="$1" 53 | else 54 | RESOURCE_PATH="${PODS_ROOT}/$1" 55 | fi 56 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 57 | cat << EOM 58 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 59 | EOM 60 | exit 1 61 | fi 62 | case $RESOURCE_PATH in 63 | *.storyboard) 64 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 65 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 66 | ;; 67 | *.xib) 68 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 69 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 70 | ;; 71 | *.framework) 72 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 73 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 74 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 75 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 76 | ;; 77 | *.xcdatamodel) 78 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 79 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 80 | ;; 81 | *.xcdatamodeld) 82 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 83 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 84 | ;; 85 | *.xcmappingmodel) 86 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 87 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 88 | ;; 89 | *.xcassets) 90 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 91 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 92 | ;; 93 | *) 94 | echo "$RESOURCE_PATH" || true 95 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 96 | ;; 97 | esac 98 | } 99 | if [[ "$CONFIGURATION" == "Debug" ]]; then 100 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker/Resources.bundle" 101 | fi 102 | if [[ "$CONFIGURATION" == "Release" ]]; then 103 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker/Resources.bundle" 104 | fi 105 | 106 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 107 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 108 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 109 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 110 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 111 | fi 112 | rm -f "$RESOURCES_TO_COPY" 113 | 114 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] 115 | then 116 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 117 | OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d) 118 | while read line; do 119 | if [[ $line != "${PODS_ROOT}*" ]]; then 120 | XCASSET_FILES+=("$line") 121 | fi 122 | done <<<"$OTHER_XCASSETS" 123 | 124 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then 125 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 126 | else 127 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" 128 | fi 129 | fi 130 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_EmojiPicker : NSObject 3 | @end 4 | @implementation PodsDummy_EmojiPicker 5 | @end 6 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double EmojiPickerVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char EmojiPickerVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker.modulemap: -------------------------------------------------------------------------------- 1 | framework module EmojiPicker { 2 | umbrella header "EmojiPicker-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker.unit-tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "EmojiPicker" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/EmojiPicker.unit-tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "EmojiPicker" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/ResourceBundle-EmojiPicker-EmojiPicker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 3.0.4 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/EmojiPicker/ResourceBundle-Resources-EmojiPicker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 3.0.9 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## EmojiPicker 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 Egor Badmaev 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | MIT License 18 | 19 | Copyright (c) 2022 Egor Badmaev 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | EmojiPicker 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_EmojiPicker_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_EmojiPicker_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/EmojiPicker/EmojiPicker.framework" 180 | fi 181 | if [[ "$CONFIGURATION" == "Release" ]]; then 182 | install_framework "${BUILT_PRODUCTS_DIR}/EmojiPicker/EmojiPicker.framework" 183 | fi 184 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 185 | wait 186 | fi 187 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_EmojiPicker_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_EmojiPicker_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker/EmojiPicker.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "EmojiPicker" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_EmojiPicker_Example { 2 | umbrella header "Pods-EmojiPicker_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example App/Pods/Target Support Files/Pods-EmojiPicker_Example/Pods-EmojiPicker_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EmojiPicker/EmojiPicker.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "EmojiPicker" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Egor Badmaev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "EmojiPicker", 7 | defaultLocalization: "en", 8 | platforms: [.iOS("11.1")], 9 | products: [ 10 | .library( 11 | name: "EmojiPicker", 12 | targets: ["EmojiPicker"] 13 | ) 14 | ], 15 | dependencies: [], 16 | targets: [ 17 | .target( 18 | name: "EmojiPicker", 19 | resources: [ 20 | .process("Resources") 21 | ] 22 | ), 23 | .testTarget( 24 | name: "EmojiPickerTests", 25 | dependencies: ["EmojiPicker"] 26 | ) 27 | ], 28 | swiftLanguageVersions: [.v4_2, .v5] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | EmojiPicker is a customizable package
implementing macOS-style emoji picker popover 4 |
5 | Emoji Picker Preview 6 |

7 | 8 | ![Swift CI badge](https://github.com/htmlprogrammist/EmojiPicker/actions/workflows/swift.yml/badge.svg) 9 | 10 | ## Navigation 11 | 12 | - [Installation](#installation) 13 | - [Swift Package Manager](#swift-package-manager) 14 | - [CocoaPods](#cocoapods) 15 | - [Manually](#manually) 16 | - [Quick Start](#quick-start) 17 | - [Usage](#usage) 18 | - [Delegate](#delegate) 19 | - [Source view](#source-view) 20 | - [Selected emoji category tint color](#selected-emoji-category-tint-color) 21 | - [Arrow direction](#arrow-direction) 22 | - [Horizontal inset](#horizontal-inset) 23 | - [Is dismissed after choosing](#is-dismissed-after-choosing) 24 | - [Custom height](#custom-height) 25 | - [Feedback generator style](#feedback-generator-style) 26 | - [Localization](#localization) 27 | - [Contributing](#contributing) 28 | - [Experiments](#experiments) 29 | 30 | ## Installation 31 | 32 | Ready for use with Swift 4.2+ on iOS 11.1+ 33 | 34 | ### Swift Package Manager 35 | 36 | The [Swift Package Manager](https://www.swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. 37 | 38 | In Xcode navigate to File → Swift Packages → Add Package Dependency. Use this URL to add the dependency: 39 | 40 | ``` 41 | ‌https://github.com/htmlprogrammist/EmojiPicker 42 | ``` 43 | 44 | Once you have your Swift package set up, adding as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. 45 | 46 | ```swift 47 | dependencies: [ 48 | .package(url: "https://github.com/htmlprogrammist/EmojiPicker", .upToNextMajor(from: "3.0.0")) 49 | ] 50 | ``` 51 | 52 | ### CocoaPods 53 | 54 | The [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate `EmojiPicker` into your Xcode project using CocoaPods, specify it in your `Podfile`: 55 | 56 | ```ruby 57 | pod 'EmojiPicker', :git => 'https://github.com/htmlprogrammist/EmojiPicker' 58 | ``` 59 | 60 | ### Manually 61 | 62 | If you prefer not to use any of dependency managers, you can integrate manually. Put `Sources/EmojiPicker` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`. 63 | 64 | ## Quick Start 65 | 66 | Create `UIButton` and add selector as action: 67 | 68 | ```swift 69 | @objc private func openEmojiPickerModule(sender: UIButton) { 70 | let viewController = EmojiPickerViewController() 71 | viewController.delegate = self 72 | viewController.sourceView = sender 73 | present(viewController, animated: true) 74 | } 75 | ``` 76 | 77 | And then recieve emoji in the delegate method: 78 | 79 | ```swift 80 | extension ViewController: EmojiPickerDelegate { 81 | func didGetEmoji(emoji: String) { 82 | emojiButton.setTitle(emoji, for: .normal) 83 | } 84 | } 85 | ``` 86 | 87 | ## Usage 88 | 89 | ### Delegate 90 | 91 | Delegate for EmojiPicker to provide chosen emoji. 92 | 93 | ```swift 94 | viewController.delegate = self 95 | ``` 96 | 97 | ### Source View 98 | 99 | A view containing the anchor rectangle for the popover. You can create any `UIView` instances and set them as the `sender`. 100 | 101 | ```swift 102 | viewController.sourceView = sender 103 | ``` 104 | 105 | Also, there is way more settings for configuration: 106 | 107 | ### Selected emoji category tint color 108 | 109 | Color for the selected emoji category. The default value of this property is `.systemBlue`. 110 | 111 | ```swift 112 | viewController.selectedEmojiCategoryTintColor = .systemRed 113 | ``` 114 | 115 | ### Arrow direction 116 | 117 | The direction of the arrow for EmojiPicker. The default value of this property is `.up`. 118 | 119 | ```swift 120 | viewController.arrowDirection = .up 121 | ``` 122 | 123 | ### Horizontal inset 124 | 125 | Inset from the `sourceView` border. The default value of this property is `0`. 126 | 127 | ```swift 128 | viewController.horizontalInset = 0 129 | ``` 130 | 131 | ### Is dismissed after choosing 132 | 133 | Defines whether to dismiss emoji picker or not after choosing. The default value of this property is `true`. 134 | 135 | ```swift 136 | viewController.isDismissedAfterChoosing = true 137 | ``` 138 | 139 | ### Custom height 140 | 141 | Custom height for EmojiPicker. The default value of this property is `nil`. 142 | 143 | ```swift 144 | viewController.customHeight = 300 145 | ``` 146 | 147 | ### Feedback generator style 148 | 149 | Feedback generator style. To turn off, set `nil` to this parameter. The default value of this property is `.light`. 150 | 151 | ```swift 152 | viewController.feedbackGeneratorStyle = .soft 153 | ``` 154 | 155 | ## Experiments 156 | 157 | To play around with the project, contribute to it, see how it works or adapt it for yourself: 158 | 159 | 1. Clone or fork this repository to yourself 160 | 2. Open `Example App/EmojiPicker.xcworkspace` file 161 | 3. Expand `Pods` target 162 | 4. Expand `Development Pods` and `EmojiPicker` directories. Here you can make your changes 163 | 5. Build & Run project to see an immediate result on an example application. Have fun! 164 | 165 | ## Localization 166 | 167 | * Chinese 🇨🇳 168 | * English 🇬🇧 169 | * French 🇫🇷 170 | * German 🇩🇪 171 | * Hindi 🇮🇳 172 | * Russian 🇷🇺 173 | * Turkish 🇹🇷 174 | * Ukrainian 🇺🇦 175 | 176 | You can also contribute your language to this list. Please, read [following heading](#contributing) for more information. 177 | 178 | > ❗️ Note that the languages are arranged in alphabetical order 179 | 180 | ## Contributing 181 | 182 | This project is based on [MCEmojiPicker](https://github.com/izyumkin/MCEmojiPicker) and is one big contribution in it. And of course contributions are welcomed and encouraged here! Please see the [Contributing guide](https://github.com/htmlprogrammist/EmojiPicker/blob/main/CONTRIBUTING.md). 183 | 184 | To be a truly great community, we need to welcome developers from all walks of life, with different backgrounds, and with a wide range of experience. A diverse and friendly community will have more great ideas, more unique perspectives, and produce more great code. We will work diligently to make our community welcoming to everyone. 185 | 186 | To give clarity of what is expected of our members, we have adopted the code of conduct defined by the Contributor Covenant. This document is used across many open source communities, and we think it articulates our values well. For more, see the [Code of Conduct](https://github.com/htmlprogrammist/EmojiPicker/blob/main/CODE_OF_CONDUCT.md). 187 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Bindings/Observable.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// A type of object that publishes a message after the object has changed. 26 | final class Observable { 27 | typealias Listener = (T) -> Void 28 | 29 | var value: T { 30 | didSet { 31 | listener?(value) 32 | } 33 | } 34 | 35 | private var listener: Listener? 36 | 37 | init(value: T) { 38 | self.value = value 39 | } 40 | 41 | func bind(_ listener: @escaping Listener) { 42 | self.listener = listener 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Extensions/Foundation/Bundle.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// We don't want `Bundle.module` that is being generated automatically for Swift Package to be overriden by our property. 26 | #if !SWIFT_PACKAGE 27 | extension Bundle { 28 | /** 29 | Resources bundle. 30 | 31 | Since CocoaPods resources bundle is something other than SPM's `Bundle.module`, we need to create it. 32 | 33 | - Note: It was named same as for Swift Package to simplify usage. 34 | */ 35 | static var module: Bundle { 36 | let path = Bundle(for: EmojiManager.self).path(forResource: "Resources", ofType: "bundle") ?? "" 37 | return Bundle(path: path) ?? Bundle.main 38 | } 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Extensions/UIKit/UIColor.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | extension UIColor { 26 | /// Color for EmojiPicker background - `.systemGroupedBackground` in UIKit. 27 | static let popoverBackgroundColor = UIColor( 28 | light: UIColor(red: 0.95, green: 0.95, blue: 0.97, alpha: 1.0), 29 | dark: UIColor(red: 0.11, green: 0.11, blue: 0.12, alpha: 1.0) 30 | ) 31 | 32 | /// Color for `selectedBackgroundView` background in `EmojiCollectionViewCell` - `.opaqueSeparator` in UIKit. 33 | static let selectedCellBackgroundViewColor = UIColor( 34 | light: UIColor(red: 0.78, green: 0.78, blue: 0.8, alpha: 1.0), 35 | dark: UIColor(red: 0.28, green: 0.28, blue: 0.29, alpha: 1.0) 36 | ) 37 | 38 | /// Color for `separatorView` background in `EmojiPickerView` - `.systemGray3` in UIKit. 39 | static let separatorColor = UIColor( 40 | light: UIColor(red: 0.78, green: 0.78, blue: 0.78, alpha: 1.0), 41 | dark: UIColor(red: 0.22, green: 0.22, blue: 0.23, alpha: 1.0) 42 | ) 43 | 44 | /// Increases brightness or decreases saturation. 45 | func adjust(by percentage: CGFloat = 30.0) -> UIColor { 46 | var h: CGFloat = 0 47 | var s: CGFloat = 0 48 | var b: CGFloat = 0 49 | var a: CGFloat = 0 50 | 51 | if self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) { 52 | if b < 1.0 { 53 | let newB: CGFloat = max(min(b + (percentage/100.0) * b, 1.0), 0.0) 54 | return UIColor(hue: h, saturation: s, brightness: newB, alpha: a) 55 | } else { 56 | let newS: CGFloat = min(max(s - (percentage/100.0) * s, 0.0), 1.0) 57 | return UIColor(hue: h, saturation: newS, brightness: b, alpha: a) 58 | } 59 | } 60 | return self 61 | } 62 | } 63 | 64 | extension UIColor { 65 | /// Supports light and dark color versions. 66 | convenience init(light: UIColor, dark: UIColor) { 67 | if #available(iOS 13.0, *) { 68 | self.init(dynamicProvider: { trait in 69 | trait.userInterfaceStyle == .light ? light : dark 70 | }) 71 | } else { 72 | self.init(cgColor: light.cgColor) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Extensions/UIKit/UIView.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | extension UIView { 26 | static var identifier: String { 27 | return String(describing: self) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Models/Category.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Egor Badmaev 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /// An object that represents category of emojis. 24 | struct Category: Decodable { 25 | /// Type-safe category type. 26 | let type: CategoryType 27 | /// Identifiers of emojis. 28 | let identifiers: [Emoji.ID] 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case type = "id" 32 | case identifiers = "emojis" 33 | } 34 | } 35 | 36 | /// Type-safe representation of emoji categories. 37 | enum CategoryType: String, Decodable, CaseIterable { 38 | case people 39 | case nature 40 | case foods 41 | case activity 42 | case places 43 | case objects 44 | case symbols 45 | case flags 46 | } 47 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Models/Emoji.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Egor Badmaev 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /// An object that represents emoji. 24 | struct Emoji: Decodable, Identifiable { 25 | /// Emoji identifier. 26 | let id: String 27 | /// Name of an emoji. 28 | let name: String 29 | /// Keywords for an emoji. 30 | let keywords: [String] 31 | /// Skin tones. 32 | let skins: [Skin] 33 | /// Version in which the emoji appeared. 34 | let version: Double 35 | /// Skin tone number. We save it so user can use the skin he chose. 36 | var skinToneIndex = 0 37 | 38 | enum CodingKeys: String, CodingKey { 39 | case id, name, keywords, skins, version 40 | } 41 | } 42 | 43 | extension Emoji { 44 | /// String emoji. For example: 😄 45 | /// 46 | /// Shows in the collection view. 47 | var emoji: String { 48 | return skins[skinToneIndex].native 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Models/EmojiSet.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Egor Badmaev 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /// An object that represents set of emojis. 24 | struct EmojiSet: Decodable { 25 | /// Emoji categories. 26 | let categories: [Category] 27 | /// Emojis dictionary from which you can get emojis by ID. 28 | let emojis: [Emoji.ID: Emoji] 29 | /// Aliases of keywords for emojis. 30 | let aliases: [String: String] 31 | } 32 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Models/PickerArrowDirectionMode.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// Specifies the direction of the popover arrow. 26 | /// 27 | /// - Important: This enum represents `UIPopoverArrowDirection` struct from UIKit, but it has only 2 directions. 28 | public enum PickerArrowDirectionMode: UInt { 29 | case up = 1 30 | case down = 2 31 | } 32 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Models/Skin.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Egor Badmaev 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /// An object that represents skin tones for emojis. 24 | struct Skin: Decodable { 25 | /// Unicode. 26 | let unified: String 27 | /// Emoji as symbol. For example: 😄 28 | let native: String 29 | } 30 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "SMILEYS & MENSCHEN"; 2 | "nature" = "TIERE & NATUR"; 3 | "foods" = "ESSEN und TRINKEN"; 4 | "activity" = "AKTIVITÄT"; 5 | "places" = "REISEN & ORTE"; 6 | "objects" = "OBJEKTE"; 7 | "symbols" = "SYMBOLE"; 8 | "flags" = "FLAGGEN"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "SMILEYS & PEOPLE"; 2 | "nature" = "ANIMALS & NATURE"; 3 | "foods" = "FOOD & DRINK"; 4 | "activity" = "ACTIVITY"; 5 | "places" = "TRAVEL & PLACES"; 6 | "objects" = "OBJECTS"; 7 | "symbols" = "SYMBOLS"; 8 | "flags" = "FLAGS"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "SMILEYS ET PERSONNES"; 2 | "nature" = "ANIMAUX ET NATURE"; 3 | "foods" = "NOURRITURE ET BOISSONS"; 4 | "activity" = "ACTIVITÉ"; 5 | "places" = "VOYAGES ET LIEUX"; 6 | "objects" = "OBJETS"; 7 | "symbols" = "SYMBOLES"; 8 | "flags" = "DRAPEAUX"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/hi.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "स्माइली और लोग"; 2 | "nature" = "पशु और प्रकृति"; 3 | "foods" = "खाद्य और पेय"; 4 | "activity" = "गतिविधि"; 5 | "places" = "यात्रा और स्थान"; 6 | "objects" = "ऑब्जेक्ट्स"; 7 | "symbols" = "प्रतीक"; 8 | "flags" = "झंडे"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "СМАЙЛИКИ И ЛЮДИ"; 2 | "nature" = "ЖИВОТНЫЕ И ПРИРОДА"; 3 | "foods" = "ЕДА И НАПИТКИ"; 4 | "activity" = "АКТИВНОСТЬ"; 5 | "places" = "ПУТЕШЕСТВИЯ И МЕСТНОСТИ"; 6 | "objects" = "ПРЕДМЕТЫ"; 7 | "symbols" = "СИМВОЛЫ"; 8 | "flags" = "ФЛАГИ"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "YÜZ İFADELERİ & İNSANLAR"; 2 | "nature" = "HAYVANLAR & DOĞA"; 3 | "foods" = "YİYECEK & İÇECEK"; 4 | "activity" = "ETKİNLİK"; 5 | "places" = "SEYAHET & YERLER"; 6 | "objects" = "NESNELER"; 7 | "symbols" = "SEMBOLLER"; 8 | "flags" = "BAYRAKLAR"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/uk.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "СМАЙЛИКИ Й ЛЮДИ"; 2 | "nature" = "ФЛОРА І ФАУНА"; 3 | "foods" = "ЇЖА І НАПОЇ"; 4 | "activity" = "АКТИВНІСТЬ"; 5 | "places" = "ПОДОРОЖІ Й МІСЦЯ"; 6 | "objects" = "ОБ'ЄКТИ"; 7 | "symbols" = "СИМВОЛИ"; 8 | "flags" = "ПРАПОРИ"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Resources/Localization/zh.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "people" = "微笑与人"; 2 | "nature" = "动物与自然"; 3 | "foods" = "食物及饮品"; 4 | "activity" = "活动"; 5 | "places" = "旅游及地点"; 6 | "objects" = "对象"; 7 | "symbols" = "符号"; 8 | "flags" = "旗帜"; 9 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Services/EmojiManager.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Egor Badmaev 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// An abstraction over entity that provides emoji set. 26 | protocol EmojiManagerProtocol { 27 | /// Provides a set of emojis. 28 | /// 29 | /// - Returns: Set of emojis. 30 | func provideEmojis() -> EmojiSet 31 | } 32 | 33 | /// The class is responsible for getting a relevant set of emojis for iOS version. 34 | final class EmojiManager: EmojiManagerProtocol { 35 | 36 | // MARK: - Private Properties 37 | 38 | /// An object that decodes instances of a data type from JSON objects. 39 | private let decoder = JSONDecoder() 40 | 41 | /// Version of emoji set. 42 | /// 43 | /// - Note: The value is `5` by default. 44 | private var emojiVersion: String { 45 | switch deviceVersion { 46 | case 12.1...13.1: 47 | return "11" 48 | 49 | case 13.2...14.1: 50 | return "12.1" 51 | 52 | case 14.2...14.4: 53 | return "13" 54 | 55 | case 14.5...15.3: 56 | return "13.1" 57 | 58 | case 15.4...: 59 | return "14" 60 | 61 | default: 62 | return "5" 63 | } 64 | } 65 | 66 | /// Version of operating system of a device. 67 | /// 68 | /// It takes major and minor version of a device and returns it as `15.5`. 69 | private var deviceVersion: Double { 70 | let operatingSystemVersion = ProcessInfo().operatingSystemVersion 71 | return Double(operatingSystemVersion.majorVersion) + Double(operatingSystemVersion.minorVersion) / 10 72 | } 73 | 74 | // MARK: - Internal Methods 75 | 76 | func provideEmojis() -> EmojiSet { 77 | guard let path = Bundle.module.path(forResource: emojiVersion, ofType: "json"), 78 | let data = try? Data(contentsOf: URL(fileURLWithPath: path)) 79 | else { 80 | fatalError("Could not get data from \"\(emojiVersion).json\" file") 81 | } 82 | 83 | guard let emojiSet = try? decoder.decode(EmojiSet.self, from: data) 84 | else { 85 | fatalError("Could not get emoji set from data: \(data)") 86 | } 87 | 88 | return emojiSet 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/ViewModel/EmojiPickerViewModel.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// Protocol for a ViewModel which is being used in `EmojiPickerViewController`. 26 | protocol EmojiPickerViewModelProtocol { 27 | /// The observed variable that is responsible for the choice of emoji. 28 | var selectedEmoji: Observable { get set } 29 | /// The observed variable that is responsible for the choice of emoji category. 30 | var selectedEmojiCategoryIndex: Observable { get set } 31 | /// The method returns the number of categories with emojis. 32 | func numberOfSections() -> Int 33 | /// The method returns the number of emojis in the target section. 34 | func numberOfItems(in section: Int) -> Int 35 | /// This method is responsible for getting the emoji for the target indexPath. 36 | func emoji(at indexPath: IndexPath) -> String 37 | /// The method is responsible for getting the localized name of the emoji section. 38 | func sectionHeaderViewModel(for section: Int) -> String 39 | } 40 | 41 | /// Emoji Picker view model. 42 | final class EmojiPickerViewModel: EmojiPickerViewModelProtocol { 43 | 44 | // MARK: - Internal Properties 45 | 46 | /// Observable object of selected emoji. 47 | var selectedEmoji = Observable(value: "") 48 | /// Observable object of selected category index of an emoji. 49 | var selectedEmojiCategoryIndex = Observable(value: 0) 50 | 51 | // MARK: - Private Properties 52 | 53 | /// Set of emojis. 54 | private let emojiSet: EmojiSet 55 | 56 | // MARK: - Init 57 | 58 | init(emojiManager: EmojiManagerProtocol) { 59 | emojiSet = emojiManager.provideEmojis() 60 | } 61 | 62 | // MARK: - Internal Methods 63 | 64 | func numberOfSections() -> Int { 65 | return emojiSet.categories.count 66 | } 67 | 68 | func numberOfItems(in section: Int) -> Int { 69 | return emojiSet.categories[section].identifiers.count 70 | } 71 | 72 | func emoji(at indexPath: IndexPath) -> String { 73 | let name = emojiSet.categories[indexPath.section].identifiers[indexPath.row] 74 | return emojiSet.emojis[name]?.emoji ?? "⚠️" 75 | } 76 | 77 | func sectionHeaderViewModel(for section: Int) -> String { 78 | return NSLocalizedString( 79 | emojiSet.categories[section].type.rawValue, 80 | bundle: .module, 81 | comment: "" 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Views/EmojiCategoryView/TouchableEmojiCategoryView.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | /// Delegate for handling touch gesture 26 | protocol EmojiCategoryViewDelegate: AnyObject { 27 | /// Processes an event by category selection. 28 | /// 29 | /// - Parameter index: Index of the selected category. 30 | func didChoiceCategory(at index: Int) 31 | } 32 | 33 | /// The class store the category icon and processes handling touches. 34 | final class TouchableEmojiCategoryView: UIView { 35 | 36 | // MARK: - Private Properties 37 | 38 | private var categoryIconView: EmojiCategoryIconView 39 | /** 40 | Insets for categoryIconView. 41 | 42 | - Note: The number `0.23` was taken based on the proportion of this element to the width of the EmojiPicker on MacOS. 43 | */ 44 | private var categoryIconViewInsets: UIEdgeInsets { 45 | let inset = bounds.width * 0.23 46 | return UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset) 47 | } 48 | /** 49 | Target category index. 50 | */ 51 | private var categoryIndex: Int 52 | 53 | private weak var delegate: EmojiCategoryViewDelegate? 54 | 55 | // MARK: - Init 56 | 57 | init( 58 | delegate: EmojiCategoryViewDelegate, 59 | categoryIndex: Int, 60 | categoryType: CategoryType, 61 | selectedEmojiCategoryTintColor: UIColor 62 | ) { 63 | self.delegate = delegate 64 | self.categoryIndex = categoryIndex 65 | self.categoryIconView = EmojiCategoryIconView( 66 | type: categoryType, 67 | selectedIconTintColor: selectedEmojiCategoryTintColor 68 | ) 69 | super.init(frame: .zero) 70 | } 71 | 72 | @available(*, unavailable, message: "init(coder:) has not been implemented") 73 | required init?(coder: NSCoder) { 74 | fatalError("init(coder:) has not been implemented") 75 | } 76 | 77 | // MARK: - Life Cycle 78 | 79 | override func layoutSubviews() { 80 | super.layoutSubviews() 81 | setupLayout() 82 | } 83 | 84 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 85 | super.touchesBegan(touches, with: event) 86 | categoryIconView.updateIconTintColor(for: .highlighted) 87 | } 88 | 89 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 90 | super.touchesEnded(touches, with: event) 91 | categoryIconView.updateIconTintColor(for: .selected) 92 | delegate?.didChoiceCategory(at: categoryIndex) 93 | } 94 | 95 | // MARK: - Public Methods 96 | 97 | /// Updates the icon state to the selected one if the indexes match and the standard one if not. 98 | /// 99 | /// - Parameter selectedCategoryIndex: Selected category index. 100 | func updateCategoryViewState(selectedCategoryIndex: Int) { 101 | categoryIconView.updateIconTintColor( 102 | for: categoryIndex == selectedCategoryIndex ? .selected : .standard 103 | ) 104 | } 105 | 106 | // MARK: - Private Methods 107 | 108 | private func setupLayout() { 109 | guard !categoryIconView.isDescendant(of: self) else { return } 110 | 111 | addSubview(categoryIconView) 112 | 113 | NSLayoutConstraint.activate([ 114 | categoryIconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: categoryIconViewInsets.left), 115 | categoryIconView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -categoryIconViewInsets.right), 116 | categoryIconView.topAnchor.constraint(equalTo: topAnchor, constant: categoryIconViewInsets.top), 117 | categoryIconView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -categoryIconViewInsets.bottom) 118 | ]) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Views/EmojiCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | final class EmojiCollectionViewCell: UICollectionViewCell { 26 | 27 | // MARK: - Private Properties 28 | 29 | private let emojiLabel: UILabel = { 30 | let label = UILabel() 31 | label.translatesAutoresizingMaskIntoConstraints = false 32 | label.font = UIFont.systemFont(ofSize: 30) 33 | label.textAlignment = .center 34 | return label 35 | }() 36 | 37 | // MARK: - Init 38 | 39 | override init(frame: CGRect) { 40 | super.init(frame: frame) 41 | 42 | setupSelectedBackgroundView() 43 | setupLayout() 44 | } 45 | 46 | required init?(coder: NSCoder) { 47 | fatalError("init(coder:) has not been implemented") 48 | } 49 | 50 | // MARK: - Public Methods 51 | 52 | public func configure(with emoji: String) { 53 | emojiLabel.text = emoji 54 | } 55 | 56 | // MARK: - Private Methods 57 | 58 | private func setupLayout() { 59 | contentView.addSubview(emojiLabel) 60 | NSLayoutConstraint.activate([ 61 | emojiLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), 62 | emojiLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), 63 | emojiLabel.topAnchor.constraint(equalTo: contentView.topAnchor), 64 | emojiLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) 65 | ]) 66 | } 67 | 68 | private func setupSelectedBackgroundView() { 69 | let selectedView = UIView() 70 | selectedView.backgroundColor = .selectedCellBackgroundViewColor 71 | selectedView.clipsToBounds = true 72 | 73 | if #available(iOS 13.0, *) { 74 | selectedView.layer.cornerCurve = .continuous 75 | } 76 | 77 | selectedView.layer.cornerRadius = 8 78 | selectedBackgroundView = selectedView 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Views/EmojiCollectionViewHeader.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | final class EmojiCollectionViewHeader: UICollectionReusableView { 26 | 27 | // MARK: - Private Properties 28 | 29 | private let headerLabel: UILabel = { 30 | let label: UILabel = UILabel() 31 | label.textColor = .systemGray 32 | label.font = UIFont.systemFont(ofSize: 14, weight: .regular) 33 | label.translatesAutoresizingMaskIntoConstraints = false 34 | return label 35 | }() 36 | 37 | // MARK: - Init 38 | 39 | override init(frame: CGRect) { 40 | super.init(frame: frame) 41 | 42 | setupLayout() 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | 49 | // MARK: - Public Methods 50 | 51 | public func configure(with text: String) { 52 | headerLabel.text = text 53 | } 54 | 55 | // MARK: - Private Methods 56 | 57 | private func setupLayout() { 58 | backgroundColor = .popoverBackgroundColor 59 | addSubview(headerLabel) 60 | 61 | NSLayoutConstraint.activate([ 62 | headerLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 7), 63 | headerLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), 64 | headerLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4) 65 | ]) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Views/EmojiPickerView.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | /// Delegate for event handling in `EmojiPickerView`. 26 | protocol EmojiPickerViewDelegate: AnyObject { 27 | /// Processes an event by category selection. 28 | /// 29 | /// - Parameter index: Index of the selected category. 30 | func didChoiceEmojiCategory(at index: Int) 31 | } 32 | 33 | final class EmojiPickerView: UIView { 34 | 35 | // MARK: - Internal Properties 36 | 37 | weak var delegate: EmojiPickerViewDelegate? 38 | 39 | var selectedEmojiCategoryTintColor: UIColor = .systemBlue 40 | 41 | let collectionView: UICollectionView = { 42 | let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() 43 | layout.sectionHeadersPinToVisibleBounds = true 44 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 45 | collectionView.verticalScrollIndicatorInsets.top = 8 46 | collectionView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) 47 | collectionView.backgroundColor = .clear 48 | collectionView.translatesAutoresizingMaskIntoConstraints = false 49 | collectionView.register( 50 | EmojiCollectionViewCell.self, 51 | forCellWithReuseIdentifier: EmojiCollectionViewCell.identifier 52 | ) 53 | collectionView.register( 54 | EmojiCollectionViewHeader.self, 55 | forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, 56 | withReuseIdentifier: EmojiCollectionViewHeader.identifier 57 | ) 58 | return collectionView 59 | }() 60 | 61 | // MARK: - Private Properties 62 | 63 | private let separatorView: UIView = { 64 | let view = UIView() 65 | view.backgroundColor = .separatorColor 66 | view.translatesAutoresizingMaskIntoConstraints = false 67 | return view 68 | }() 69 | 70 | private let categoriesStackView: UIStackView = { 71 | let stackView = UIStackView() 72 | stackView.distribution = .fillEqually 73 | stackView.backgroundColor = .popoverBackgroundColor 74 | stackView.translatesAutoresizingMaskIntoConstraints = false 75 | return stackView 76 | }() 77 | 78 | private var categoryViews = [TouchableEmojiCategoryView]() 79 | private var selectedCategoryIndex: Int = .zero 80 | 81 | /// Describes height for `categoriesStackView`. 82 | /// 83 | /// - Note: The number `0.13` was taken based on the proportion of this element to the width of the EmojiPicker on macOS. 84 | private var categoriesStackViewHeight: CGFloat { bounds.width * 0.13 } 85 | private var categoriesStackHeightConstraint: NSLayoutConstraint? 86 | 87 | // MARK: - Init 88 | 89 | override init(frame: CGRect) { 90 | super.init(frame: .zero) 91 | setupCategoryViews() 92 | } 93 | 94 | @available(*, unavailable, message: "init(coder:) has not been implemented") 95 | required init?(coder: NSCoder) { 96 | fatalError("init(coder:) has not been implemented") 97 | } 98 | 99 | // MARK: - Override 100 | 101 | override func draw(_ rect: CGRect) { 102 | super.draw(rect) 103 | 104 | setupView() 105 | } 106 | 107 | /// Passes the index of the selected category to all categoryViews to update the state. 108 | /// 109 | /// - Parameter categoryIndex: Selected category index. 110 | func updateSelectedCategoryIcon(with categoryIndex: Int) { 111 | selectedCategoryIndex = categoryIndex 112 | categoryViews.forEach { 113 | $0.updateCategoryViewState(selectedCategoryIndex: categoryIndex) 114 | } 115 | } 116 | 117 | // MARK: - Private Methods 118 | 119 | private func setupView() { 120 | addSubview(collectionView) 121 | addSubview(categoriesStackView) 122 | addSubview(separatorView) 123 | 124 | let categoriesStackHeightConstraint = categoriesStackView.heightAnchor.constraint( 125 | equalToConstant: categoriesStackViewHeight 126 | ) 127 | self.categoriesStackHeightConstraint = categoriesStackHeightConstraint 128 | 129 | NSLayoutConstraint.activate([ 130 | collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), 131 | collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), 132 | collectionView.topAnchor.constraint(equalTo: topAnchor, constant: safeAreaInsets.top), 133 | collectionView.bottomAnchor.constraint(equalTo: separatorView.topAnchor), 134 | 135 | categoriesStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), 136 | categoriesStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), 137 | categoriesStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -safeAreaInsets.bottom), 138 | categoriesStackHeightConstraint, 139 | 140 | separatorView.leadingAnchor.constraint(equalTo: leadingAnchor), 141 | separatorView.trailingAnchor.constraint(equalTo: trailingAnchor), 142 | separatorView.topAnchor.constraint(equalTo: categoriesStackView.topAnchor), 143 | separatorView.heightAnchor.constraint(equalToConstant: 1) 144 | ]) 145 | } 146 | 147 | private func setupCategoryViews() { 148 | backgroundColor = .popoverBackgroundColor 149 | categoriesStackHeightConstraint?.constant = categoriesStackViewHeight 150 | categoryViews = [] 151 | categoriesStackView.subviews.forEach { $0.removeFromSuperview() } 152 | 153 | var index = 0 154 | for type in CategoryType.allCases { 155 | let categoryView = TouchableEmojiCategoryView( 156 | delegate: self, 157 | categoryIndex: index, 158 | categoryType: type, 159 | selectedEmojiCategoryTintColor: selectedEmojiCategoryTintColor 160 | ) 161 | 162 | /// We need to set _selected_ state for the first category (default at the start). 163 | categoryView.updateCategoryViewState(selectedCategoryIndex: selectedCategoryIndex) 164 | categoryViews.append(categoryView) 165 | categoriesStackView.addArrangedSubview(categoryView) 166 | index += 1 167 | } 168 | } 169 | 170 | /// Scrolls collection view to the header of selected category. 171 | /// 172 | /// - Parameter section: Selected category index. 173 | private func scrollToHeader(for section: Int) { 174 | guard let cellFrame = collectionView.collectionViewLayout.layoutAttributesForItem(at: IndexPath(item: 0, section: section))?.frame, 175 | let headerFrame = collectionView.collectionViewLayout.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: section))?.frame 176 | else { return } 177 | 178 | let offset = CGPoint(x: -collectionView.contentInset.left, y: cellFrame.minY - headerFrame.height) 179 | collectionView.setContentOffset(offset, animated: false) 180 | } 181 | } 182 | 183 | // MARK: - EmojiCategoryViewDelegate 184 | 185 | extension EmojiPickerView: EmojiCategoryViewDelegate { 186 | 187 | func didChoiceCategory(at index: Int) { 188 | scrollToHeader(for: index) 189 | delegate?.didChoiceEmojiCategory(at: index) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Sources/EmojiPicker/Views/EmojiPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2022 Ivan Izyumkin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | /// Delegate protocol to match user interaction with emoji picker. 26 | public protocol EmojiPickerDelegate: AnyObject { 27 | /// Provides chosen emoji. 28 | /// 29 | /// - Parameter emoji: String emoji. 30 | func didGetEmoji(emoji: String) 31 | } 32 | 33 | /// Emoji Picker view controller. 34 | public final class EmojiPickerViewController: UIViewController { 35 | 36 | // MARK: - Public Properties 37 | 38 | /// Delegate for selecting an emoji object. 39 | public weak var delegate: EmojiPickerDelegate? 40 | 41 | /// The view containing the anchor rectangle for the popover. 42 | public var sourceView: UIView? { 43 | didSet { 44 | popoverPresentationController?.sourceView = sourceView 45 | } 46 | } 47 | 48 | /** 49 | The direction of the arrow for EmojiPicker. 50 | 51 | - Note: The default value of this property is `.up`. 52 | */ 53 | public var arrowDirection: PickerArrowDirectionMode = .up 54 | 55 | /** 56 | Custom height for EmojiPicker. 57 | 58 | - Note: The default value of this property is `nil`. 59 | - Important: it will be limited by the distance from `sourceView.origin.y` to the upper or lower bound(depends on `permittedArrowDirections`). 60 | */ 61 | public var customHeight: CGFloat? 62 | 63 | /** 64 | Inset from the sourceView border. 65 | 66 | - Note: The default value of this property is `0`. 67 | */ 68 | public var horizontalInset: CGFloat = 0 69 | 70 | /** 71 | A boolean value that determines whether the screen will be hidden after the emoji is selected. 72 | 73 | If this property’s value is `true`, the EmojiPicker will be dismissed after the emoji is selected. 74 | If you want EmojiPicker not to dismissed after emoji selection, you must set this property to `false`. 75 | 76 | - Note: The default value of this property is `true`. 77 | */ 78 | public var isDismissedAfterChoosing: Bool = true 79 | 80 | /** 81 | Color for the selected emoji category. 82 | 83 | - Note: The default value of this property is `.systemBlue`. 84 | */ 85 | public var selectedEmojiCategoryTintColor: UIColor? = .systemBlue { 86 | didSet { 87 | guard let selectedEmojiCategoryTintColor = selectedEmojiCategoryTintColor else { return } 88 | emojiPickerView.selectedEmojiCategoryTintColor = selectedEmojiCategoryTintColor 89 | } 90 | } 91 | 92 | /** 93 | Feedback generator style. To turn off, set `nil` to this parameter. 94 | 95 | - Note: The default value of this property is `.light`. 96 | */ 97 | public var feedbackGeneratorStyle: UIImpactFeedbackGenerator.FeedbackStyle? { 98 | didSet { 99 | guard let feedBackGeneratorStyle = feedbackGeneratorStyle else { 100 | feedbackGenerator = nil 101 | return 102 | } 103 | feedbackGenerator = UIImpactFeedbackGenerator(style: feedBackGeneratorStyle) 104 | } 105 | } 106 | 107 | // MARK: - Private Properties 108 | 109 | /// View of this controller. 110 | private let emojiPickerView = EmojiPickerView() 111 | /// An obect that creates haptics to simulate physical impacts. 112 | private var feedbackGenerator: UIImpactFeedbackGenerator? 113 | /// View model of this module. 114 | private var viewModel: EmojiPickerViewModelProtocol 115 | 116 | // MARK: - Init 117 | 118 | /// Creates EmojiPicker view controller with provided configuration. 119 | public init() { 120 | let emojiManager = EmojiManager() 121 | viewModel = EmojiPickerViewModel(emojiManager: emojiManager) 122 | 123 | super.init(nibName: nil, bundle: nil) 124 | modalPresentationStyle = .popover 125 | 126 | setupDelegates() 127 | bindViewModel() 128 | } 129 | 130 | required init?(coder: NSCoder) { 131 | fatalError("init(coder:) has not been implemented") 132 | } 133 | 134 | // MARK: - Life Cycle 135 | 136 | override public func loadView() { 137 | view = emojiPickerView 138 | } 139 | 140 | override public func viewDidLoad() { 141 | super.viewDidLoad() 142 | 143 | setupPreferredContentSize() 144 | setupArrowDirections() 145 | setupHorizontalInset() 146 | } 147 | 148 | // MARK: - Private Methods 149 | 150 | private func bindViewModel() { 151 | viewModel.selectedEmoji.bind { [unowned self] emoji in 152 | feedbackGenerator?.impactOccurred() 153 | delegate?.didGetEmoji(emoji: emoji) 154 | 155 | if isDismissedAfterChoosing { 156 | dismiss(animated: true, completion: nil) 157 | } 158 | } 159 | 160 | viewModel.selectedEmojiCategoryIndex.bind { [unowned self] categoryIndex in 161 | self.emojiPickerView.updateSelectedCategoryIcon(with: categoryIndex) 162 | } 163 | } 164 | 165 | private func setupDelegates() { 166 | emojiPickerView.delegate = self 167 | emojiPickerView.collectionView.delegate = self 168 | emojiPickerView.collectionView.dataSource = self 169 | presentationController?.delegate = self 170 | } 171 | 172 | /// Sets up preferred content size. 173 | /// 174 | /// - Note: The number `0.16` was taken based on the proportion of height to the width of the EmojiPicker on macOS. 175 | private func setupPreferredContentSize() { 176 | let size: CGSize = { 177 | switch UIDevice.current.userInterfaceIdiom { 178 | case .phone: 179 | let sideInset: CGFloat = 20 180 | let screenWidth: CGFloat = UIScreen.main.nativeBounds.width / UIScreen.main.nativeScale 181 | let popoverWidth: CGFloat = screenWidth - (sideInset * 2) 182 | // The number 0.16 was taken based on the proportion of height to the width of the EmojiPicker on macOS. 183 | let heightProportionToWidth: CGFloat = 1.16 184 | return CGSize( 185 | width: popoverWidth, 186 | height: popoverWidth * heightProportionToWidth 187 | ) 188 | default: 189 | // macOS size 190 | return CGSize(width: 410, height: 460) 191 | } 192 | }() 193 | 194 | preferredContentSize = size 195 | } 196 | 197 | private func setupArrowDirections() { 198 | popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection( 199 | rawValue: arrowDirection.rawValue 200 | ) 201 | } 202 | 203 | private func setupHorizontalInset() { 204 | guard let sourceView = sourceView else { return } 205 | 206 | popoverPresentationController?.sourceRect = CGRect( 207 | x: 0, 208 | y: popoverPresentationController?.permittedArrowDirections == .up ? horizontalInset : -horizontalInset, 209 | width: sourceView.frame.width, 210 | height: sourceView.frame.height 211 | ) 212 | } 213 | } 214 | 215 | // MARK: - UICollectionViewDataSource, UICollectionViewDelegate 216 | 217 | extension EmojiPickerViewController: UICollectionViewDataSource, UICollectionViewDelegate { 218 | 219 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 220 | return viewModel.numberOfSections() 221 | } 222 | 223 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 224 | return viewModel.numberOfItems(in: section) 225 | } 226 | 227 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 228 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EmojiCollectionViewCell.identifier, for: indexPath) as? EmojiCollectionViewCell 229 | else { return UICollectionViewCell() } 230 | 231 | cell.configure(with: viewModel.emoji(at: indexPath)) 232 | return cell 233 | } 234 | 235 | public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 236 | switch kind { 237 | case UICollectionView.elementKindSectionHeader: 238 | guard let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: EmojiCollectionViewHeader.identifier, for: indexPath) as? EmojiCollectionViewHeader 239 | else { return UICollectionReusableView() } 240 | 241 | sectionHeader.configure(with: viewModel.sectionHeaderViewModel(for: indexPath.section)) 242 | return sectionHeader 243 | default: 244 | return UICollectionReusableView() 245 | } 246 | } 247 | 248 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 249 | collectionView.deselectItem(at: indexPath, animated: true) 250 | viewModel.selectedEmoji.value = viewModel.emoji(at: indexPath) 251 | } 252 | } 253 | 254 | // MARK: - UIScrollViewDelegate 255 | 256 | extension EmojiPickerViewController: UIScrollViewDelegate { 257 | 258 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 259 | /// This code updates the selected category during scrolling. 260 | let indexPathsForVisibleHeaders = emojiPickerView.collectionView.indexPathsForVisibleSupplementaryElements( 261 | ofKind: UICollectionView.elementKindSectionHeader 262 | ).sorted(by: { $0.section < $1.section }) 263 | 264 | if let selectedEmojiCategoryIndex = indexPathsForVisibleHeaders.first?.section, 265 | viewModel.selectedEmojiCategoryIndex.value != selectedEmojiCategoryIndex { 266 | viewModel.selectedEmojiCategoryIndex.value = selectedEmojiCategoryIndex 267 | } 268 | } 269 | } 270 | 271 | // MARK: - UICollectionViewDelegateFlowLayout 272 | 273 | extension EmojiPickerViewController: UICollectionViewDelegateFlowLayout { 274 | 275 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { 276 | return CGSize(width: collectionView.frame.width, height: 40) 277 | } 278 | 279 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 280 | let sideInsets = collectionView.contentInset.right + collectionView.contentInset.left 281 | let contentSize = collectionView.bounds.width - sideInsets 282 | return CGSize(width: contentSize / 8, height: contentSize / 8) 283 | } 284 | 285 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 286 | return 0 287 | } 288 | 289 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 290 | return 0 291 | } 292 | } 293 | 294 | // MARK: - EmojiPickerViewDelegate 295 | 296 | extension EmojiPickerViewController: EmojiPickerViewDelegate { 297 | 298 | func didChoiceEmojiCategory(at index: Int) { 299 | feedbackGenerator?.impactOccurred() 300 | viewModel.selectedEmojiCategoryIndex.value = index 301 | } 302 | } 303 | 304 | // MARK: - UIAdaptivePresentationControllerDelegate 305 | 306 | extension EmojiPickerViewController: UIAdaptivePresentationControllerDelegate { 307 | 308 | public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { 309 | return .none 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Here provided ideas or features which will be implemented soon. 4 | 5 | - [x] The main functionality for choosing emojis 6 | - [x] Dark mode 7 | - [x] Segmented control for jumping an emoji section 8 | - [x] Automatic adjustment of the relevant set of emoji for the iOS version 9 | - [ ] Select skin tones from popup 10 | - [ ] Search bar and search results 11 | - [ ] Recently used 12 | -------------------------------------------------------------------------------- /Tests/EmojiPickerTests/CategoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryTests.swift 3 | // EmojiPicker-Unit-Tests 4 | // 5 | // Created by Егор Бадмаев on 25.01.2023. 6 | // 7 | 8 | import XCTest 9 | @testable import EmojiPicker 10 | 11 | class CategoryTests: XCTestCase { 12 | 13 | var category: EmojiPicker.Category! 14 | 15 | override func tearDownWithError() throws { 16 | category = nil 17 | } 18 | 19 | func test_decodeCategory_success() throws { 20 | category = try? JSONDecoder().decode(EmojiPicker.Category.self, from: category1) 21 | 22 | XCTAssertNotNil(category, "The result of decoding should be successful") 23 | XCTAssertEqual(category.type, CategoryType.people) 24 | XCTAssertEqual(category?.identifiers, ["grinning", "smiley", "smile"]) 25 | } 26 | 27 | func test_decodeCategory_wrongCodingKeys1() throws { 28 | category = try? JSONDecoder().decode(EmojiPicker.Category.self, from: category2) 29 | 30 | XCTAssertNil(category) 31 | } 32 | 33 | func test_decodeCategory_wrongCodingKeys2() throws { 34 | category = try? JSONDecoder().decode(EmojiPicker.Category.self, from: category3) 35 | 36 | XCTAssertNil(category) 37 | } 38 | 39 | func test_decodeCategory_wrongCodingKeys3() throws { 40 | category = try? JSONDecoder().decode(EmojiPicker.Category.self, from: category4) 41 | 42 | XCTAssertNil(category) 43 | } 44 | } 45 | 46 | fileprivate let category1 = Data(""" 47 | { 48 | "id": "people", 49 | "emojis": [ 50 | "grinning", 51 | "smiley", 52 | "smile", 53 | ] 54 | } 55 | """.utf8) 56 | 57 | fileprivate let category2 = Data(""" 58 | { 59 | "type": "people", 60 | "identifiers": [ 61 | "grinning", 62 | "smiley", 63 | "smile", 64 | ] 65 | } 66 | """.utf8) 67 | 68 | fileprivate let category3 = Data(""" 69 | { 70 | "id": "people", 71 | "identifiers": [ 72 | "grinning", 73 | "smiley", 74 | "smile", 75 | ] 76 | } 77 | """.utf8) 78 | 79 | fileprivate let category4 = Data(""" 80 | { 81 | "type": "people", 82 | "emojis": [ 83 | "grinning", 84 | "smiley", 85 | "smile", 86 | ] 87 | } 88 | """.utf8) 89 | -------------------------------------------------------------------------------- /Tests/EmojiPickerTests/EmojiManagerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiManagerTests.swift 3 | // EmojiPickerTests 4 | // 5 | // Created by Егор Бадмаев on 25.01.2023. 6 | // 7 | 8 | import XCTest 9 | @testable import EmojiPicker 10 | 11 | class EmojiManagerTests: XCTestCase { 12 | 13 | /// SUT. 14 | var emojiManager: EmojiManager! 15 | 16 | override func setUpWithError() throws { 17 | emojiManager = EmojiManager() 18 | } 19 | 20 | override func tearDownWithError() throws { 21 | emojiManager = nil 22 | } 23 | 24 | /** 25 | Tests providing emoji set. 26 | 27 | Due to the fact that the tests can be run on different devices, we have to check that the answer does not come to us an empty array. 28 | */ 29 | func testProvideEmojisMethod() throws { 30 | let result = emojiManager.provideEmojis() 31 | 32 | XCTAssertGreaterThan(result.emojis.count, 0) 33 | XCTAssertGreaterThan(result.categories.count, 0) 34 | XCTAssertGreaterThan(result.aliases.count, 0) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/EmojiPickerTests/EmojiPickerViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiPickerViewModelTests.swift 3 | // EmojiPickerTests 4 | // 5 | // Created by Егор Бадмаев on 13.01.2023. 6 | // 7 | 8 | import XCTest 9 | @testable import EmojiPicker 10 | 11 | class EmojiPickerViewModelTests: XCTestCase { 12 | 13 | var emojiManagerStub: EmojiManagerStub! 14 | /// SUT. 15 | var viewModel: EmojiPickerViewModel! 16 | 17 | override func setUpWithError() throws { 18 | emojiManagerStub = EmojiManagerStub() 19 | viewModel = EmojiPickerViewModel(emojiManager: emojiManagerStub) 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | viewModel = nil 24 | emojiManagerStub = nil 25 | } 26 | 27 | /// Tests default values for selected emoji. 28 | func testSelectedEmojiDefaultValues() throws { 29 | XCTAssertEqual(viewModel.selectedEmoji.value, "") 30 | XCTAssertEqual(viewModel.selectedEmojiCategoryIndex.value, 0) 31 | } 32 | 33 | func testNumberOfSectionsMethod() throws { 34 | let result = viewModel.numberOfSections() 35 | 36 | XCTAssertEqual(result, emojiManagerStub.emojiSet.categories.count) 37 | } 38 | 39 | func testNumberOfItemsMethod() throws { 40 | let section = 0 41 | 42 | let result = viewModel.numberOfItems(in: 0) 43 | 44 | XCTAssertEqual(result, emojiManagerStub.emojiSet.categories[section].identifiers.count) 45 | } 46 | 47 | func testEmojiAtIndexPathMethod() throws { 48 | let indexPath = IndexPath(row: 1, section: 0) 49 | 50 | let result = viewModel.emoji(at: indexPath) 51 | 52 | let expectedResult = emojiManagerStub.emojiSet.emojis[ 53 | emojiManagerStub.emojiSet.categories[indexPath.section].identifiers[indexPath.row] 54 | ]?.emoji 55 | XCTAssertEqual(result, expectedResult) 56 | } 57 | 58 | func testSectionHeaderViewModelMethod() throws { 59 | let section = 0 60 | 61 | let result = viewModel.sectionHeaderViewModel(for: section) 62 | 63 | let expectedResult = NSLocalizedString( 64 | emojiManagerStub.emojiSet.categories[section].type.rawValue, 65 | bundle: .module, 66 | comment: "" 67 | ) 68 | XCTAssertEqual(result, expectedResult) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/EmojiPickerTests/Mocks/EmojiPickerDelegateMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiPickerDelegateMock.swift 3 | // EmojiPickerTests 4 | // 5 | // Created by Егор Бадмаев on 13.01.2023. 6 | // 7 | 8 | @testable import EmojiPicker 9 | 10 | class EmojiPickerDelegateMock: EmojiPickerDelegate { 11 | func didGetEmoji(emoji: String) { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/EmojiPickerTests/ObservableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObservableTests.swift 3 | // EmojiPickerTests 4 | // 5 | // Created by Егор Бадмаев on 13.01.2023. 6 | // 7 | 8 | import XCTest 9 | @testable import EmojiPicker 10 | 11 | class ObservableTests: XCTestCase { 12 | 13 | var observable: Observable! 14 | var newValue: Int! 15 | 16 | override func setUpWithError() throws { 17 | observable = Observable(value: 0) 18 | } 19 | 20 | override func tearDownWithError() throws { 21 | observable = nil 22 | newValue = nil 23 | } 24 | 25 | func testChangingValueWithBinding() throws { 26 | let newNumber = 1 27 | observable.bind { [unowned self] number in 28 | newValue = number 29 | } 30 | 31 | observable.value = newNumber 32 | 33 | XCTAssertEqual(newValue, newNumber) 34 | } 35 | 36 | func testChangingValueWithoutBinding() throws { 37 | let newNumber = 1 38 | 39 | observable.value = newNumber 40 | 41 | XCTAssertNotEqual(newValue, newNumber) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/EmojiPickerTests/PickerArrowDirectionModeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerArrowDirectionModeTests.swift 3 | // EmojiPickerTests 4 | // 5 | // Created by Егор Бадмаев on 13.01.2023. 6 | // 7 | 8 | import XCTest 9 | @testable import EmojiPicker 10 | 11 | class PickerArrowDirectionModeTests: XCTestCase { 12 | 13 | var arrowDirection: PickerArrowDirectionMode! 14 | 15 | override func tearDownWithError() throws { 16 | arrowDirection = nil 17 | } 18 | 19 | func testSuccessInitValue1() throws { 20 | arrowDirection = PickerArrowDirectionMode(rawValue: 1) 21 | 22 | XCTAssertEqual(PickerArrowDirectionMode.up, arrowDirection) 23 | } 24 | 25 | func testSuccessInitValue2() throws { 26 | arrowDirection = PickerArrowDirectionMode(rawValue: 2) 27 | 28 | XCTAssertEqual(PickerArrowDirectionMode.down, arrowDirection) 29 | } 30 | 31 | func testFailureInitValue0() throws { 32 | arrowDirection = PickerArrowDirectionMode(rawValue: 0) 33 | 34 | XCTAssertNil(arrowDirection) 35 | } 36 | 37 | func testFailureInitAllLeftValues() throws { 38 | for i in 3...100 { 39 | arrowDirection = PickerArrowDirectionMode(rawValue: UInt(i)) 40 | XCTAssertNil(arrowDirection) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/EmojiPickerTests/Stubs/EmojiManagerStub.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiManagerStub.swift 3 | // EmojiPicker 4 | // 5 | // Created by Егор Бадмаев on 25.01.2023. 6 | // 7 | 8 | @testable import EmojiPicker 9 | 10 | class EmojiManagerStub: EmojiManagerProtocol { 11 | 12 | func provideEmojis() -> EmojiSet { 13 | emojiSet 14 | } 15 | 16 | var emojiSet = EmojiSet( 17 | categories: [ 18 | Category(type: .people, identifiers: [ 19 | "smile", 20 | "laughing", 21 | "grin" 22 | ]), 23 | Category(type: .foods, identifiers: [ 24 | "peach" 25 | ]), 26 | ], 27 | emojis: [ 28 | "smile": Emoji( 29 | id: "smile", 30 | name: "Grinning Face with Smiling Eyes", 31 | keywords: [], 32 | skins: [ 33 | Skin(unified: "1f604", native: "😄") 34 | ], 35 | version: 1.0 36 | ), 37 | "laughing": Emoji( 38 | id: "laughing", 39 | name: "Grinning Squinting Face", 40 | keywords: [], 41 | skins: [ 42 | Skin(unified: "1f606", native: "😆") 43 | ], 44 | version: 1.0 45 | ), 46 | "grin": Emoji( 47 | id: "grin", 48 | name: "Beaming Face with Smiling Eyes", 49 | keywords: [], 50 | skins: [ 51 | Skin(unified: "1f601", native: "😁") 52 | ], 53 | version: 1.0 54 | ), 55 | "peach": Emoji( 56 | id: "peach", 57 | name: "Peach", 58 | keywords: [], 59 | skins: [ 60 | Skin(unified: "1f351", native: "🍑") 61 | ], 62 | version: 1.0 63 | ), 64 | ], 65 | aliases: [:] 66 | ) 67 | } 68 | --------------------------------------------------------------------------------