Что такое Ångström Style System?
В обычном мобильном приложении интерфейс — одна из самых сложных и самых важных частей. То, что видят пользователи, что оценивают в первую очередь заказчики, при приёме работы.
Даже если дизайн рисуется до разработки, в процессе создания первой версии всё-равно появляется огромное количество изменений. Тут кнопочку перекрасить, тут подвинуть, там шрифт увеличить/уменьшить, потому что не поместилось и так далее. Каждое из этих изменений требует доработки. Часто таких доработок бывает несколько сотен или даже тысяч, если не получается сразу подобрать правильные параметры.
В такой ситуации очень выгодным получается отделить настройки дизайна от самого интерфейса и настраивать независимо. Эту задачу должна решать система стилей, идеологически — как CSS для HTML.
В статье я покажу, как именно я стараюсь организовать стили в приложении, и какими средствами я для этого пользуюсь.
Структура стилевой системы в приложении
Когда я разрабатываю приложение, обычно получается два слоя стилевых настроек. Первый слой содержит самое базовое:
- цвета приложения,
- шрифты приложения,
- базовые стили текстов приложения,
- базовые параметры. Например, общий радиус скругления углов, или какие-то шаги отступов для сетки.
Это те параметры, которыми пронизано всё приложение. Если вдруг дизайнер решил, что он выбрал неудачный шрифт и его требуется поменять, то поменять нужно сразу везде. Этот слой обычно достаточно небольшой и понятный всем. Можно отдать поредактировать/понастраивать его дизайнерам или даже, в особо доверенных случаях, заказчику.
Из этих параметров в огромном количестве вариаций создаётся второй слой стилей. Тут уже описываются конкретные экраны, конкретные компоненты интерфейса. Например.
- лейбл-раз: шрифт номер два, цвет красный-три, выравнивание влево,
- кнопка-два: шрифт пять, цвет синий-активный, выравнивание по центру.
Файлы второго слоя обычно объемные, их удобно разбить на несколько, по экранам или разделам приложения.
Достоинства стилей в приложении
Стили, особенно организованные в два слоя, очень хорошо отделяют настройки интерфейса от кода. Проще найти в нескольких стилевых файлах, где настройка вот этого размера шрифта, чем искать по сотням исходников или огромным сторибордам.
В случае, если научиться загружать стили из удаленного источника (из файла в ДропБоксе), то в сотни раз ускоряется настройка дизайна при удалённой разработке. Одно дело, когда дизайнер может сесть вместе с разработчиком и попытаться быстро поднастроить какой-то параметр, а когда это невозможно? Каждая итерация может занимать часы и дни. В данном же случае обновил файл, перезапустил приложение (или сделал хитрый секретный жест), посмотрел результат.
В некоторых ситуациях удобно использовать скины. Для читалок книжек это, например, необходимость, читать днём удобно со светлым фоном, ночью — с тёмным. Иногда этого хочет заказчик, иногда по дизайну требуется для «вау-эффекта». Если сделать два варианта первого слоя стилей, то можно их переключать, оставляя второй слой одним и тем же. В результате скины получатся почти бесплатно.
Когда появилась необходимость в системе стилей?
Необходимость в системе стилей появилась сразу с двух сторон:
- в аутсорсинговой компании разрабатывается множество приложений. Приятно иметь общую схему их работы, чтобы один разработчик мог поправить код другого. И заказчики постоянно меняющие требования — тоже встречаются чаще, чем хотелось бы.
- в разработке своих приложений хочется всегда сделать «идеально». Когда мы разрабатывали Ангстрем с Ильёй Бирманом, требовался механизм настройки дизайна, который бы позволил Илье играться с настройками сложных компонентов, таких, как кастомный курсор.
Я предпочитаю тестировать новые идеи на своих собственных проектах, а не на проектах заказчика, поэтому Ангстрему повезло, и я создал для него систему стилей. В ней для описания стилей использовался файл на JSON, в который я добавил возможностей включения других файлов (в том числе «из Дропбокса»). Во фреймворке также были реализованы колбеки, которые сообщали, что стиль изменился. Я подключил обновление стилей на тряску Айфона, получилось занятно. Поправил файл в Саблайме, сохранил, трясанул Айфон, стили подгрузились и применились ко всему приложению.
Недостатки существующей системы, что «не взлетело»?
В целом идея оказалась настолько удачная, что она быстро распространилась на все наши создаваемые приложения. В некоторых случаях это позволило сильно сократить время разработки (когда дизайнер заказчика, к примеру, хотел поднастроить шрифты или цвета), иногда дизайн с момента разработки до релиза менялся целиком несколько раз, практически полностью без участия разработчика (или с минимальным участием, чтобы настроить тонкие моменты).
Но несколько моментов мне не понравились.
Во-первых, JSON. Постоянно получалось, что случайно забыл запятую или сделал другую глупую ошибку в файле, и ломались все стили. JSON не проверяет названия пропертей (это просто строки и ему всё-равно, что там написано), типизация введена искусственно, что тоже мешает раннему нахождению ошибок, и так далее.
Тот же JSON заставил меня кодировать типы данных прямо в названиях. Цвета у меня заканчивались на color, точки — на center или point, и так далее. В результате названия полей получались сильно длинее, чем требуется.
После первых попыток использования стилевой системы, выяснилось, что самый удобный вариант — когда по JSON’у создаётся иерархия классов, полностью повторяющая иерархию объектов в JSON’е. В приложении тогда появляется строго-типизированная структура, к которой можно удобно обращаться как к обычному коду. В принципе, можно было привязать к обновлению стилей UI-компоненты, чтобы стиль знал, что он применяется вот к этой кнопке, и если вдруг обновился (скачался новый стиль из Дропбокса), то сам бы кнопку и обновил. Но на деле оказалось, что такая магия только мешает жить, лучше прописывать правила обновления явно.
В-третьих, я не создал консольную утилиту, которая бы генерила классы стилей. А сами стили пересоздавались при запуске приложения в симуляторе. Это оказалось существенным минусом, как при подключении стилей в проект, так и при последующем их обновлении.
Также немного мешал Objective-C и необходимость поддержки старых версий iOS. Код сгенерированного класса получался большой и неприятный, очень хотелось бы его оптимизировать.
S2 — более простая и эффективная система стилей
Поняв недостатки, я попробовал улучшить систему с тем, чтобы она лучше подходила для решаемых задач, по всем параметрам:
- Теперь формат файла — KTV. Он поддерживает и ссылки, и миксины, и типы, включая базовый тип color. KTV умеет без изменений читать и JSON-файлы, поэтому мои старые стили перекочевали в новую структуру без изменений.
- Файл стиля создается из ktv-файла на Swift. При этом, если не использовать поддержку Objective-C (которая тоже возможна), то получается компактный, удобный как для просмотра так и для использования класс (иерархия вложенных классов), не засоряющих глобальное пространство имён приложения.
- Благодаря поддержке типов в KTV, стало возможным отказаться от суффиксов в названиях, что упростило имена пропертей и классов.
- Появилась консольная утилита, которая может по ktv-файлу (или нескольким файлам) создать классы стиля.
Для примера приведу (очень небольшой) фрагмент исходного KTV:
{ maxWidthForIPad: 600 darkTheme: false defaultFontName: HelveticaNeue-Light bolderFontName: HelveticaNeue boldFontName: HelveticaNeue-Medium defaultSymbolFontName: AngstromSymbols-Light bolderSymbolFontName: AngstromSymbols basicColors: { plateBackground: #edf5f4 separators: #00407020 } ilya: { aboutBackground: @basicColors.plateBackground listSeparator: @basicColors.separators } colors: { about: { background: @ilya.aboutBackgroundColor separator: @ilya.listSeparatorColor } } about: { margins: [0, 0, 0, 0] separatorSpacing: 10 background: @colors.about.backgroundColor separator: @colors.about.separatorColor } }
И соответствующий ему фрагмент стилевого файла, который получается:
let S2 = CONStyle() public struct CONStyle: S2Object { private static let _rootStyle = S2 public struct BasicColors: S2Object { let separators = UIColor(colorLiteralRed:Float(1.0), green:Float(1.0), blue:Float(1.0), alpha:Float(0.0)) let plateBackground = UIColor(colorLiteralRed:Float(0.929411764705882), green:Float(0.929411764705882), blue:Float(0.929411764705882), alpha:Float(1.0)) } let basicColors = BasicColors() public struct Ilya: S2Object { let aboutBackground = _rootStyle.basicColors.plateBackground let listSeparator = _rootStyle.basicColors.separators } let ilya = Ilya() public struct Colors: S2Object { public struct About: S2Object { let background = _rootStyle.ilya.aboutBackgroundColor let separator = _rootStyle.ilya.listSeparatorColor } let about = About() } public struct About: S2Object { let margins = UIEdgeInsets(top:0.0, left:0.0, bottom:0.0, right:0.0) let separatorSpacing = Int(10) let background = _rootStyle.colors.about.backgroundColor let separator = _rootStyle.colors.about.separatorColor } }
Конечно же, использование Swift будет неудобно для полностью Objective-C проектов. Это потащит за собой в проект Swift-рантайм, который может существенно увеличить размер приложения. Но, во-первых, не сильно сложно написать генератор чисто Objective-C кода, а во-вторых, всё сейчас идёт к массовому использованию Swift в приложениях, так что и генерируемый S2 код будет «в тему».
ссылка на оригинал статьи https://habrahabr.ru/post/278787/
Добавить комментарий