Как я использую Ångström Style System? S2

от автора


Что такое Å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/


Комментарии

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

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