Хабрахабр

Анимации в iOS-приложениях, рождённые на сервере

Среди прочего она позволяет пользователям выразить благодарность любимым стримерам в виде подарков. Полгода назад мы представили одну из самых впечатляющих функций Badoo — прямые трансляции. А чтобы было ещё интереснее, мы планировали обновлять подарки и анимации каждые несколько недель. Мы хотели сделать эти подарки максимально яркими и привлекательными, поэтому решили их оживить — другими словами, анимировать.

Для этого в каждом релизе должны быть задействованы Android- и iOS-команды, а вкупе со временем, необходимым на одобрение обновления в App Store, это означает, что запуск каждого релиза с обновлёнными анимациями может занять несколько дней. iOS-инженеры наверняка догадались, о каких объёмах работы идёт речь: чтобы удалять старые и добавлять новые анимации, необходимо совершить множество действий с клиентской стороны. Однако нам удалось решить эту проблему, и сейчас я расскажу как.

К тому времени мы уже умели экспортировать анимации Adobe After Effects (далее — AAE) в понятный нашему iOS-приложению формат при помощи библиотеки Lottie. В этот раз мы пошли чуть дальше: решили хранить все актуальные анимации на сервере и скачивать их по мере необходимости.

Пример реальной анимации в нашем приложении, полученной таким способом:

Она не такая креативная, как в Badoo, но вполне подходит для демонстрации нашего подхода. Однако в этом посте в качестве примера я возьму простенькую анимацию, которую создал сам.

AAE-проект, который я использую, можно найти вместе с другими исходниками на GitHub. Итак, открыв проект, расположенный по адресу _raw/animations/Fancy/Fancy.aep, вы увидите окно:

Сейчас я расскажу не о процессе создания анимаций в AAE, а о том, как импортировать уже существующие анимации из AAE в подходящий для iOS-приложения формат при помощи плагина Bodymovin.

Установив плагин, откройте его, выбрав в меню Window/Extensions/Bodymovin:

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

В настройках анимации мы можем попросить Bodymovin включить ресурсы в JSON-файл, выбрав пункт Assets / Include in json:

Наконец, нажатием кнопки Render экспортируем и сохраняем в файл выбранную анимированную композицию.

Предположим, что мы загрузили JSON-файлы отрендеренных анимаций на веб-сервер. В нашем случае для простоты я поместил их в репозиторий проекта на GitHub. Анимации доступны здесь:

Базовая ссылка https://raw.githubusercontent.com/chupakabr/server-provided-animations/master/_raw/rendered-animations/

Идентификаторы анимаций:

  • clouds.json
  • fireworks.json

Решение доступно здесь, а подробное объяснение — в этой статье. Примечание: ищете написанный на Swift веб-сервер для анимаций?

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

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

Загрузка анимаций

Учитывая, что REST API для получения данных уже готов, пришло время ввести протокол поставщика данных и добавить его имплементацию, которая скачивает данные с сервера:

import Lottie protocol AnimationsProviderProtocol { typealias Completion = (_ animation: LOTComposition?) -> Void func loadAnimation(byId id: String, completion: @escaping Completion)
} final class ServerAnimationProvider: AnimationsProviderProtocol func loadAnimation(byId id: String, completion: @escaping Completion) { let path = "/\(id).json" guard let animationUrl = URL(string: path, relativeTo: self.endpoint) else { completion(nil) return } URLSession.shared.invalidateAndCancel() let task = URLSession.shared.dataTask(with: animationUrl) { (data, response, error) in guard error == nil, let data = data, let json = self.parseJson(from: data) else { completion(nil) return } let animation = LOTComposition(json: json) completion(animation) } task.resume() } private func parseJson(from data: Data?) -> [AnyHashable : Any]? { guard let data = data else { return nil } do { let json = try JSONSerialization.jsonObject(with: data, options: []) as? [AnyHashable : Any] return json } catch { return nil } }
}

Предположим, что мы следуем паттерну MVVM, — тогда его легко использовать в сущности ViewModel следующим образом: Этот класс поставщика данных позволяет нам по запросу загружать с сервера анимации в формате JSON и хранить их в памяти для отрисовки на UI.

// ... private let animationProvider: AnimationsProviderProtocol private(set) var animationModel: LOTComposition? // … func loadAnimation(byId animationId: String) { self.animationProvider.loadAnimation(byId: animationId) { [weak self] (animationModel) in self?.animationModel = animationModel } } // ...

Эти данные используются слоем представления для отображения анимации. ViewModel обновляет свойство выбранной анимации при получении корректного HTTP-ответа от сервера с непустым JSON-объектом.

Слой представления

Теперь мы можем использовать ViewModel для получения доступа к данным анимации и отображать их на UI при помощи встроенного обработчика действия on tap, привязанного к кнопке:

class ViewController: UIViewController { // ... @IBOutlet weak var animationContainer: UIView! override func viewDidLoad() { super.viewDidLoad() // ... self.animationView = { let view = LOTAnimationView(frame: self.animationContainer.bounds) self.animationContainer.addSubview(view) return view }() } @IBAction func onPlayAnimationAction(_ sender: Any) { self.animationView.stop() self.animationView.sceneModel = self.viewModel.animationModel self.animationView.play() }
}

При нажатии на кнопку экземпляр LOTAnimationView обновляется с помощью свежих данных из ViewModel.

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

Теперь в приложении отображается анимация, загруженная из нашего REST API
(с сервера). Вот и всё.

Хитрости:

  • AAE поддерживает большинство типов объектов, включая растровые и векторные изображения;
  • Bodymovin позволяет внедрять в конечный JSON-файл все ресурсы при помощи Base64 и благодаря этому можно избежать загрузки ресурсов отдельно на клиентской стороне;
  • вы можете либо рисовать прямо в векторе в AAE либо просто импортировать векторные изображения формата Adobe Illustrator.

К сожалению, у меня не получилось импортировать в AAE файлы формата SVG (я пытался!).

Узнать больше о хитростях и решении возможных проблем вы можете из этой интересной статьи моего коллеги Радослава Сесивы.

Итак, что нам даёт загрузка анимаций с сервера? Самое очевидное преимущество этого подхода — возможность разделить всех участников процесса обновления анимации. Иными словами, чтобы выпустить новую крутую анимацию, дизайнерам достаточно предоставить серверной команде соответствующий JSON-файл. Чтобы удалить анимацию на клиенте, достаточно просто удалить её с сервера. Легко и быстро.

Ещё очень здорово, что одни и те же функции можно реализовать на всех поддерживаемых платформах (iOS, Android, Web), не внося изменений в клиент-серверный протокол, серверный код и в сами файлы анимаций непосредственно на клиенте.

Спасибо за внимание!
На этом всё.

Полезные ссылки

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть