1-й в Украине сертификационный курс по UX от UXQB — крупнейшего в мире комьюнити UX специалистов
×Закрыть

Векторные сцены и анимации — как побороть сегментацию в iOS

Привет, меня зовут Виталий Малаховский, я инженер в компании Genesis.

Недавно мне довелось поработать над интересной задачей: сделать анимацию для iOS приложения, которая будет сохранять пропорции при изменении размера (то есть быть векторной). И, конечно, она не должна терять при этом качество ресурсов. Порывшись в интернете, я нашел несколько способов сделать это:

  • Нарисовать анимацию в Adobe After Effects, а потом легко мигрировать на любую платформу (iOS / macOS / Android), используя Lottie, — супервариант для нас как для разработчиков (потому что, по сути, и делать ничего не надо). Но для этого нужно, чтобы кто-нибудь знал After Effects, поэтому мы его не рассматривали.
  • Использовать векторные ресурсы и относительные значения при работе с UIKit, — это именно то, о чём я вам расскажу.

Все примеры работы с ресурсами, которые я вам покажу, будут на Zeplin. Но если вы не пользуетесь Zeplin — не беда. Самостоятельно найдите размеры ресурсов в любой другой удобной для вас программе.

Векторные ресурсы

Мы будем использовать PDF формат ресурсов — это векторный формат, поэтому мы можем масштабировать их настолько, насколько нам нужно, — а значит одним файлом можно пользоваться для всех разрешений. Для этого загляните в xcassets и найдите необходимый PDF — или добавьте его туда сразу, если его там еще нет 😬 Проверьте, чтобы было выбрано «Single Scale» в подменю «Scales» и отмечен «Preserve Vector Data». На этом с ресурсами закончили.

Относительные значения

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

Прежде всего, поместите все элементы в геометрическую фигуру. Если навести курсор на область рядом с изображением, Zeplin покажет вам прямоугольник/квадрат, в который оно вписано, или размеры до края экрана. Тогда можете ориентироваться на них. Запишите/запомните размер геометрической фигуры в пикселях. В моем случае это — прямоугольник 264×233.

Теперь нам нужно найти размеры всех наших элементов и их центральные точки относительно только что найденной фигуры. Советую экспортировать все элементы в виде отдельных картинок, а затем посмотреть на их размеры, поскольку, иногда, когда вы нажимаете на изображение в Zeplin, он показывает размер какой-то части, а не изображения целиком. На примере видно, как Zeplin показывает вилку без тени, а на уже экспортированном изображении — вилка уже с тенью.

Дальше нужно найти центральные точки. Если вы ищете размеры относительно края экрана, то просто в верхнем углу вы найдете точки Х и Y. Если же вы ищете середину относительно фигуры, в которой вписаны элементы (то есть не от краев экрана), нажмите на эту фигуру, а затем наведите мышкой на конкретную картинку, середину которой вы ищете. Вы увидите расстояние до границ. Запомнили. Теперь добавьте половину ширины к Х и половину высоты к Y. Это и есть центр фигуры. Опять же, значения могут быть неправильные, если у вас есть тень или что-то подобное. Поэтому всегда лучше обратиться к своему дизайнеру. В моём случае нет ничего, что может повлиять на значения.

Пример:
Размер моего изображения — 26×117, Х и Y — 36 и 58 соответственно.
Центр по горизонтали = Х + «ширина» / 2 = 36 + 26/2 = 49.
Центр по вертикали = Y + «высота» / 2 = 58 + 117/2 = ~117.
Центр = 49×117.

Layout constraints

Теперь мы можем начать работу с XCode. Создайте UIView, где будут располагаться все элементы. При желании, можете сделать размер UIView такой же, как и у фигуры, относительно которой мы меряли размеры. 264×233 — в моем случае.

Добавьте UIImageView и установите ей PDF в качестве изображения. Установите «layout constraints» для ширины и высоты, относительно супервью. Значения констант для всех наших «layout constraints» должны равняться 0, но вот множители (multipliers) должны выражать соотношения между размером грани изображения и размером грани фигуры, в которую мы вписываем наши изображения.

На пальцах: множитель для высоты выражает соотношения высоты изображения и высоты супервью, множитель для ширины — соотношение ширины изображения и ширины супервью.

Пример:
Размер моего изображения — 26×117, размер супервью — 264×233.
Множитель для высоты = «высота изображения» : «высоты супервью» = 117:233.
Множитель для ширины = «ширина изображения» : «ширина супервью» = 26:264.

Теперь нам нужно установить позицию для наших изображений. Она тоже будет в относительных величинах. Для этого мы, собственно, и заморачивались с центральными точками, которые рассчитали ранее. Установите «layout constraints» для изображения, чтобы оно стояло ровно по центру супервью (center horizontally in container & center vertically in container). Значения констант должны равняться 0. Множители должны иметь следующее значение: желаемый центр оси изображения к центру оси фигуры, в которую мы вписываем изображение.

Пример:
Желаемый центр изображения = 49×117.
Размер супервью = 264×233, соответственно центр суперью = 132×117.
Множитель по горизонтали = «центр изображения по оси Х» : «центр супервью по оси Х» = 49:132.
Множитель по вертикали = «центр изображения по оси Y» : «центр супервью по оси Y» = 117:117 или просто 1.

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

Анимация

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

Требования: вилка должна сместиться на 30 пикселей влево. Относительное значение будет следующим: «смещение» / («ширина» / 100%) = 30 / (233/100) = ~13%. Чтобы использовать наш результат в коде, мы должны умножить это относительное значение на значение ширины супесью (то есть сделать обратное вычисление).

Пример кода:

private func newTableClothAnimator() -> UIViewPropertyAnimator {
    return UIViewPropertyAnimator(duration: 0, curve: .easeInOut) {
        self.tableCloth.transform = CGAffineTransform(translationX: -(self.frame.width * 0.13), y: 0)
        }
}

Итоги

  • Экономия на ресурсах — мы используем один PDF-файл, вместо 3-х png-картинок.
  • Сцена, которая выглядит идентично на любом iOS устройстве.
  • Анимация, которая выглядит идентично на любом iOS устройстве.

Короткий пример того что получилось 👇

Если вам интересно увидеть приложение целиком — можете найти его по ссылке.

6 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

С одной стороны напоминает о буханке хлеба с которой можно сделать троллейбус, а с другой — о том как всё плохо с ресурсами на нынешних платформах.

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

Шаг в правильную сторону. Лет пять уже придерживаюсь мнения, что PDF лучший формат 2D—ассетов: есть и вектор, и растр, и текст, и js для параметризации. И, в отличие от html—based разбродов—шатаний на разных движках, после растеризации имеем pixel—perfect картинку на всех платформах.

Следующий шаг — уход от кучи отдельных PDF—ассетов в сторону áтласа.

Ребя, которые ниже блеснули юмористическими скилами, не нашел ваших статей для сравнения. Ану покажите свои «яйца».

Подготовка к подаче на визу О1? =)

Недавно мне довелось поработать над интересной задачей

Не хардкодить пиксели для анимации это действительно очень интересная задача.

Подписаться на комментарии