В ходе работы над мобильным приложением разработчики получают от дизайнеров макеты, шрифты и изображения. Преобразование этих материалов в код — нудное занятие, да и в процессе каждый норовит изобрести свой велосипед. Чтобы проекты оставались чистыми, мы приняли единую конвенцию по работе со стилями.

Цветовая схема




Это первый кандидат на кодогенерацию. Мы решили передавать список цветов в формате, который легко набрать руками и так же легко распарсить, чтобы сгенерировать категорию UIColor.
Дизайнеры собирают простой текстовый файл #COLOR — TITLE. Разработчики включают этот файл в проект, а скрипт перед компиляцией генерирует файлы с кодом и палитрой в формате CLR. Такую палитру можно использовать для работы в Interface Builder.

#B3ED3B green
#5ABBF2 blue
#FD86C9 pink
#FD4737 red
#FEAE30 orange




Нюансы


В использовании палитры есть несколько проблем.

Первая заключается в том, что панель Colors не обновляет список доступных палитр, как и их содержимое. Каждое приложение в OS X может иметь свой экземпляр панели Colors, и чтобы вновь сгенерированная палитра была добавлена в панель или обновилась, необходимо перезапустить всё приложение.
Поэтому мы написали небольшой плагин для Xcode, который после каждого билда передергивает все пользовательские палитры в панели Colors.

Вторая проблема происходит из формата, в котором Interface Builder сохраняет установленный для элемента цвет:

<color key="textColor" red="0.29803922772407532" green="0.85098040103912354" blue="0.39215686917304993" alpha="1" colorSpace="deviceRGB"/>


Голые цифры, и ни одного намёка на палитру — а значит, если мы её обновим, в xib/storyboard всё останется по-старому. К сожалению, эту проблему нам решить не удалось, но мы определили два возможных подхода:

  1. Не использовать палитры. На все элементы провешивать Outlet’ы и красить кодом. Проблема неактуального представления элементов в Interface Builder частично решается технологией IBDesignable.
  2. Использовать решение в лоб. Если необходимо заменить цвет, пробегаться по xml, который стоит за каждым xib или storyboard, и заменять совпадения на новый цвет. Как, например, здесь. Не самое изящное решение, но работает.

Нашу утилиту для генерации категории UIColor и плагин для перезагрузки списка палитр панели Colors вы можете найти на GitHub — RMRColorTools-iOS.

С плагином всё просто. Необходимо клонировать репозиторий и запустить на исполнение таргет RMRRefreshColorPanelPlugin, а затем перезапустить Xcode.

Для автоматической генерации UIColor при каждом билде на вкладке Build Phases добавьте Run Script Phase с вызовом утилиты RMRHexColorGen.





Шрифты




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

@implementation UIFont (RMRFonts)

+ (UIFont *)rmr_regularFontOfSize:(CGFloat)size
{
   return [UIFont fontWithName:@"HelveticaNeue"
                          size:size];
}

+ (UIFont *)rmr_mediumFontOfSize:(CGFloat)size
{
   return [UIFont fontWithName:@"HelveticaNeue-Medium"
                          size:size];
}

#pragma mark - Style fonts

+ (UIFont *)rmr_fontA1 { return [self rmr_mediumFontOfSize:17.f]; }

+ (UIFont *)rmr_fontA2 { return [self rmr_regularFontOfSize:17.f]; }

+ (UIFont *)rmr_fontB1 { return [self rmr_regularFontOfSize:14.f]; }

+ (UIFont *)rmr_fontB2 { return [self rmr_regularFontOfSize:11.f]; }

+ (UIFont *)rmr_fontB3 { return [self rmr_mediumFontOfSize:10.f]; }

@end


Провешивать на каждый label outlet для установки шрифта — даже звучит не очень. Поэтому мы реализовали сабкласс UILabel с установкой стиля при инициализации. Стиль устанавливается в конкретном сабклассе.

@implementation RMRLabel

- (void)prepareAppearance
{
   // Абстрактный метод для переопределения наследниками
}

- (instancetype)initWithFrame:(CGRect)frame
{
   if (self = [super initWithFrame:frame]) {
       [self prepareAppearance];
   }

   return self;
}

- (void)awakeFromNib
{
   [self prepareAppearance];
}

@end

@implementation RMRLabelA1

- (void)prepareAppearance { self.font = [UIFont rmr_fontA1]; }

@end


Это позволяет определять установленный стиль, задавая конкретный сабкласс UILabel в Interface Builder.

Профит от такой схемы (Font ? UlLabel ? IB) — если потребуется стиль изменить, то вся работа будет локализована в единственном файле.

Изображения


В Xcode 6 мы получили возможность использовать векторные изображения в формате pdf. Вкупе с tint color и возможностью использовать изображения как шаблон мы получаем простой механизм, который позволяет забыть о танцах с ресурсами, если потребовалось на полтона изменить цвет иконок.




Заключение


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


Читайте также:
Архитектурный дизайн мобильных приложений: часть 2
Архитектурный дизайн мобильных приложений: часть 1
Что может быть проще кнопки?

Комментарии (6)


  1. egormerkushev
    11.04.2015 15:50

    Хотелось бы услышать отзыв о такой штуке как Classy — classy.as


    1. firmach Автор
      13.04.2015 13:55

      Мы не применяем Classy в проектах, поэтому, к сожалению, я не могу дать значимых комментариев про эту систему.


      1. egormerkushev
        13.04.2015 13:56

        Спасибо за ответ!
        Может кто-то еще поделится опытом…


  1. Piskov
    13.04.2015 22:16
    +1

    С PDF для ресурсов не всё так радужно.


    1. Flanker_4
      14.04.2015 16:12

      Печально, но наверное нужно уточнить, что не для рессуров, а для asset catalog'a


  1. Flanker_4
    14.04.2015 16:04

    Насчет UILabel
    в awakeFromNib лучше вызвать super
    Или переопределить -initWithCoder (по аналогии с initWithFrame)
    Еще, как вариант, можно использовать appearance (собственно для этого он и предназначался), а стиль устанавливать для всех label в отдельном методе аля

    [[CustomLabelClass appearance] setFont:[UIFont customFont]];
    

    Ну или в самом классе CustomLabelClass в методе
    + (void)load;