Как стать автором
Обновить

Элемент выбора эмодзи (aka EmojiPicker) для iOS как в MacOS

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров3K

Выбор эмодзи - обычное явление в приложениях, потому что они стали популярным способом самовыражения пользователей в цифровом виде. Они могут быть использованы для придания индивидуальности и эмоций текстовым сообщениям или дополнительной смысловой окраски различным элементам приложения.

К примеру, в моем приложении Timetable, которое предназначено для ведения учебного расписания, нужно было добавить возможность прикреплять смайлик к названию расписания.

UI настройки названия расписания
UI настройки названия расписания

Сделать это системным способом можно только с помощью UITextField или UITextViewкостыль с типом клавиатуры. В моем случае это не подходило и я начал искать другие способы. Нашел несколько библиотек, но все из них мне не подошли по разным причинам. Где-то не хватало локализации и были различные баги, где-то UI не подходил и так далее. Главное для меня было, чтобы этот элемент был в стилистике Apple, корректно работал и был локализован на все языки.

Подходящие решение нашел в MacOS. Там как раз есть системный элемент, который появляется в нужном тебе месте и имеет стрелочку, которая показывает какой элемент является отправной точкой.

Элемент выбора эмодзи в MacOS
Элемент выбора эмодзи в MacOS

В итоге принял решение сделать подобный элемент для iOS. Далее расскажу с какими трудностями столкнулся и какие решения использовал во время разработки.

Поиск списка эмодзи

Для начала стал искать упорядоченный список смайликов, который разбит по категориям. Сперва посмотрел в сторону системных решений от Apple, но, к сожалению, никаких списков они не дают. После этого, полез в интернет и сходу нашел кучу списков, но после, чуть более детального изучения, вылезла проблема с обратной совместимостью эмодзи.

То есть, эмодзи добавленный в iOS 15, на всех предыдущих версиях будет черным квадратом с вопросительным знаком по центру, а во всех подобных списках лежат просто массивы со строковыми значениями в виде эмодзи. Какие версии iOS они поддерживают - никто не знает ??‍♂️

И если я хочу поддерживать не только последнюю версию iOS, мне нужно найти список, где указано когда был добавлен смайлик. Первая мысль была, что они так и разбиты по версиям iOS, но углубляясь вспомнил, что есть же еще и Android…

Оказалось, что эмодзи разделяются на свои Unicode версии:

Список Unicode версий эмодзи
Список Unicode версий эмодзи

Этот список находится на сайте - unicode.com. Там же и лежат нужные мне файлы со списками эмодзи, но там нет локализации, только английский язык ? Но об этом позже.

emoji-test.txt файл для версии эмодзи 15.0
emoji-test.txt файл для версии эмодзи 15.0

В данном файле эмодзи состоят из одного или нескольких шестнадцатеричных значений. Для того, чтобы получить из этих значений сам эмодзи, необходимо преобразовать это в массив шестнадцатеричных значений и сделать с ними следующие:

print(
    [0x1F600]
      // Преобразуем шестнадцатеричное значение в 32-битное целочисленное представление нашего смайлика в таблице Unicode.
      .map({ UnicodeScalar($0) })
      // Убираем опционал.
      .compactMap({ $0 })
      // Преобразуем 32-битное целое число в символ для правильного представления.
      .map({ String($0) })
      // Объединяем все значения, чтобы получить окончательный смайлик.
      .joined()
) // "?"

После того как научился преобразовывать шестнадцетиричное представление смайлика в строку, мне нужно было конвертировать этот список в упорядоченный массив для взаимодействия с ним уже из кода.

Для того, чтобы из файла emoji-test.txt получить модель, быстро на коленке написал парсер, который проходился по строкам, разбивая их на нужные мне значения. И в итоге получил массив эмодзи в нужной мне модели:

MCEmoji(
    // Массив шестнадцетиричных ключей.
    emojiKeys: [0x1F600],
    // Флаг, который отражает, доступны ли у этого смайлика разные оттенки кожи.
    isSkinToneSupport: false,
    // Версия эмодзи.
    version: 1.0
)

Дальше отсортировал их по категориям и после этого упорядоченный список, который я так долго искал, был готов ?

Обратная совместимость

После того, как был готов упорядоченный список с указанными Unicode версиями для каждого эмодзи, нужно было соотнести версии Unicode и iOS.

Тут нет какого-то супер нативного способа, сделал это спустя несколько часов изучения сайта emojipedia.org. Там есть подобного формата новости: “In March 2022 iOS 15.4 included brand new emojis from Emoji 14.0” по которым получилось все соотнести.

Вот как это выглядит в коде:

private let maxCurrentAvailableEmojiVersion: Double = {
	let currentIOSVersion = (UIDevice.current.systemVersion as NSString).floatValue
	switch currentIOSVersion {
	case 12.1...13.1:
		return 11.0
	case 13.2...14.1:
		return 12.0
	case 14.2...14.4:
		return 13.0
	case 14.5...15.3:
		return 13.1
	case 15.4...:
		return 14.0
	default:
		return 5.0
	}
}()

Теперь для того, чтобы нужные эмодзи отображались для нужной версии iOS, мне осталось проверить, что версия эмодзи меньше, либо равна maxCurrentAvailableEmojiVersion.

Выбор тона кожи

Перед началом реализации логики выбора тона кожи, первой мыслью было хранить массив эмодзи со всеми видами тона кожи для каждого (P.S. Так многие и делают. Ну, а как по другому?).

После очередного просмотра списка эмодзи, я увидел одну закономерность. Она заключалась в том, что если смайлик поддерживает выбор тона кожи, то все элементы массива остаются неизменными, но на второе место в массиве вставляется определенное значение для каждого тона кожи.

И эта закономерность повторяется для каждого смайлика, у которого есть возможность выбрать тон кожи.

Оказалось, что действительно внизу этого файла есть раздел “Components”, где лежит список шестнадцатеричных значений для каждого тона кожи:

Это спасло меня от большого дублирования смайликов с разными типами кожи. Ведь теперь можно подставлять на второе место в массив значений эмодзи - значение нужного тона кожи и в итоге получу желаемый смайлик. А хранить в модели только изначальный массив значений ?

После выбора тона кожи, MCEmojiSkinTone.rawValue сохраняется в UserDefaults для нужного смайлика, который и является ключем для записи.

После этого, можно обновить модель и получить эмодзи с выбранным тоном кожи.

Двусоставные смайлики

Однако, есть еще и «двусоставные» эмодзи.

Элемент выбора тона кожи для «двусоставных» смайликов в iOS
Элемент выбора тона кожи для «двусоставных» смайликов в iOS

На фото видно, что для левой и для правой стороны эмодзи, в верхней части этого элемента - есть своя строка с тонами кожи. И на каждой строке противоположная сторона эмодзи закрашена сплошным цветом.

К сожалению, тона кожи, который закрашивал бы сплошным цветом смайлик в том файле не было ?

Я подозреваю, что для упрощения существует подобный код, который можно подставить и эмодзи станет закрашенным. Но пока я его не нашел. Поэтому не смог сделать этот элемент.

P.S. Если вы знаете решение этой проблемы, пожалуйста, свяжитесь со мной ? Очень хочу сделать этот элемент тоже.

UI. Что тут могло пойти не так?

Иконки для категорий

Так как хотелось сделать элемент идентичным, иконки для категорий тоже должны были быть такими же.

Оригинальные иконки категорий из MacOS
Оригинальные иконки категорий из MacOS

Оказалось, найти их не так уж и просто. Они не лежат по первому запросу в гугле и даже если мне как-то удавалось их найти, они были в плохом качестве.

После долгих поисков, по всевозможным ресурсам, мне удалось их найти в системном, приватном фреймворке MacOS. Путь к желаемой папке выглядит так: /System/Library/PrivateFrameworks/CharacterPicker.framework/Versions/A/Resources ? Они лежат там в отличном качестве, в формате pdf.

Но я не хотел тянуть в библиотку изображения и хотел попрактиковаться в отрисовке кодом. В этом мне очень помогла программа PaintCode. Так получилось убить сразу двух зайцев: уменьшить количество файлов библиотеки и попрактиковаться с UIBezierPath.

После практики с отрисовкой категорий, верстка элемента превью и выбора тона кожи, уже казалась вполне реальной задачей.

Элемент превью и выбора тона кожи

Элемент выбора тона кожи для эмодзи в MacOS ожидаемо выглядит не в стиле iOS.

Элемент выбора тона кожи в MacOS
Элемент выбора тона кожи в MacOS

Поэтому решил взять этот элемент, а за одно и элемент превью смайлика из стандартной клавиатуры на iPhone.

Элементы выбора тона кожи и превью из iOS
Элементы выбора тона кожи и превью из iOS

С элементом превью все было легко: фиксированные размеры и фиксированная позиция по центру нажатой ячейки.

Но вот с элементом выбора тона кожи все было чуть сложнее. Так как уже нужно было учесть:

  • Расположение выбранного эмодзи по X(исходя из этого значения выбирается направление верхнего прямоугольника с типами кожи).

  • Так же нужно было отследить, чтобы верхний прямоугольник не выходил за края самого пикера.

  • Правильно обработать все жесты.

Код верстки и расчетов получился слишком большой для того, чтобы его вставить в статью, поэтому оставлю ссылки: элемент выбора тона кожиэлемент превью.

Там осталась одна нерешенная проблема. В реализации выбора тона кожи от Apple можно начать выбор сразу, как только этот элемент появляется, не отрывая палец от экрана. Двигая палец вправо или влево будет подсвечиваться тон кожи, который совпадает по X с расположением пальца на экране.

Я повторил этот функционал, но у меня не получилось сделать так, чтобы можно было не отрывая палец после нажатия, сразу обработать событие перемещения по экрану. Ни один из жестов в моем случае так и не заработал, одновременно с жестами нажатия и логикой презентации этого элемента.

Вы можете попробовать исправить это в репозитории проекта и отправить pull-request. Буду очень рад, если у вас получится!

Локализация

С локализацией тоже было все непросто. Если локализацию ключей для поиска смайликов я с трудом, но нашел, то локализованного списка названий, для категорий эмодзи, у меня никак не получилось найти.

Локализовывать через переводчик не хотел, так как в таком случае 100% были бы ошибки.

Решение было очень топорным, но 100% давало мне верный перевод. Я вручную несколько часов сидел, переключал язык на телефоне, делал скриншот названий всех 9 категорий. Благо iPhone научился распознавать текст на фото и можно было копировать названия с полученных изображений(но все же не все языки так просто поддавались и приходилось иногда подключать Google Translate для определения корректного текста на фото).

В итоге, получилось собрать локализацию для всех стандартных языков в iOS.

На этом приключения не закончились. В библиотеке я хотел поддерживать сразу два менеджера зависимостей: Swift Package Manager и CocoaPods. Но локализация в них настраивается по разному.

В Swift Package Manager обращение к ресурсу локализации выглядит так:

NSLocalizedString(”a_localized_string”, bundle: Bundle.module, comment: “a comment”)

В качестве параметра bundle передается Bundle.module. Этот статический параметр автоматически добавляет сам SPM.

CocoaPods такого не умеет. Там для доступа к ресурсу локализации нужно сделать следующее:

let path = Bundle(for: LibraryName.self).path(
	forResource: "LibraryName",
	ofType: "bundle"
) ?? ""
NSLocalizedString(”a_localized_string”, bundle: Bundle(path: path) ?? Bundle.main, comment: “a comment”)

Эту проблему удалось решить с помощью расширения для Bundle, которое будет добавляться только если библиотека используется не через Swift Package Manager:

#if !SWIFT_PACKAGE
extension Bundle {
    static var module: Bundle {
        let path = Bundle(for: MCUnicodeManager.self).path(
            forResource: "MCEmojiPicker",
            ofType: "bundle"
        ) ?? ""
        return Bundle(path: path) ?? Bundle.main
    }
}
#endif

Это расширение позволяет всегда иметь доступ к Bundle.module в библиотеке, независимо от того, через какой менеджер зависимостей она используется.

Заключение

В итоге, у меня получилась библиотека, которая позволяет буквально в 3 строчки кода добавить элемент выбора эмодзи в ваше приложение.

Плюсом к этому она:

  • Поддерживает Swift Package Manager и CocoaPods.

  • Весит всего 795 килобайт.

  • И единственная из аналогов поддерживает все стандартные локализации(на момент публикации статьи, конечно ?).

Превью работы MCEmojiPicker
Превью работы MCEmojiPicker

Не ожидал, что такой простой, с первого взгляда, элемент станет таким интересным вызовом.

Дальше мне предстоит работа по оптимизации, так как оказалось, что отображать эмодзи в UILabel - затратная задача для оперативной памяти.

А так же, из не реализованных стандартных функций осталось добавить: поиск и раздел недавно использованных смайликов. Их рассчитываю доделать уже в ближайшее время.

Посмотреть код и поставить ⭐️, если понравилась статья можно тут:

Спасибо за внимание. Буду рад комментариям и предложениям.

Теги:
Хабы:
Всего голосов 12: ↑12 и ↓0+12
Комментарии5

Публикации

Истории

Работа

Swift разработчик
37 вакансий
iOS разработчик
27 вакансий

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
24 – 25 октября
One Day Offer для AQA Engineer и Developers
Онлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
26 октября
ProIT Network Fest
Санкт-Петербург
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань