Å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 код будет «в тему».

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      0
      Думаю тут стоило идти по пути совместимости с CSS, а не JSON.
        0
        Про CSS я думал. Получилось, что они очень плохо ложатся на UI мобильных приложений. Впрочем, попытки применения CSS к ним уже были (например, https://nativecss.com).
          0
          А в чём сложности?
            0
            Никаких сложностей, просто, по моему мнению, CSS не подходит для нативных UI мобильных устройств, чем я занимаюсь.
              0
              Почему это?
                0
                Короткий ответ — потому что он сделан для другого. Каскадность совершенно ни к чему, нет, как таковой, иерархии блоков, нет DOM'а, чтобы стили привязывать к элементам, лейаут делается совершенно по иным правилам. Это только те причины, которые на поверхности, реализовывать CSS для нативного использования тоже не очень приятно.
                  +1
                  Не путайте CSS как язык, и CSS как синтаксис. В чём проблема взять синтаксис и часть языковых средств (определения цветов, отступов), выкинув ненужное (каскад, стандартные раскладки) и добавив недостающее (например, вложенные определения из less/scss/stylus)?
                    0
                    Извините, я никак понять не могу. Вы в принципе не признаёте иных решений, кроме тех, которые знаете, или просто желаете набросить?

                    Если вам требуется CSS, используйте конечно. Я ведь не заставляю использовать свою систему. Для этого и существуют разные варианты, чтобы иметь возможность выбрать нужный в конкретном случае. Впрочем, можно всегда пользоваться только микроскопом, это тоже валидное решение.
                      0
                      Вы зря воспринимаете критику в штыки.
                        0
                        В ваших комментариях нет критики :-)

      Only users with full accounts can post comments. Log in, please.