Даже для самого что ни на есть начинающего разработчика (скорее, на которого и рассчитан данный очерк), надеюсь, не секрет, что в коде не должно присутствовать никаких т.н. «hardcoded»-значений и прочих всяких там «magic numbers». Почему – тоже, надеюсь, понятно, а если нет, то в Сети имеются десятки, а то и сотни статей на эту тему, а также написан классический труд. «Android Studio» (наверное, не во всех случаях, но все же) даже любит генерировать «warnings» на эту тему и предлагать выносить строки и т.д. в ресурсные файлы. «Xcode» (пока?) такими подсказками нас не балует, и разработчику приходится самостоятельно держать себя в узде или, скажем, получать по рукам от коллег после «code review».
Все это касается и используемых в приложении цветов.
Для начала хочется дать несколько более или менее стандартных рекомендаций.
Во-первых, цвета всех элементов лучше сразу задавать в коде, а не в «Storyboard»/«Interface Builder». Если, конечно, это не приложение с одним экраном с тремя элементами и в одной цветовой схеме. Но даже и в этом случае никогда не знаешь наверняка, как изменится ситуация в будущем.
Во-вторых, все цвета стоит определить константами, вынесенными в отдельный файл. Или в отдельную группу по соседству с соответствующим «view»-кодом.
В-третьих, цвета стоит разделять на категории. Т.е. оперировать не «цветом второй кнопки на первом экране», а чем-нибудь вроде «цвета фона основного типа кнопок».
Если от дизайнера (или от собственного чувства вкуса) поступит сигнал изменить цвет какого-либо элемента, его не придется долго искать – раз, изменять в нескольких местах (забывая какое-то из них и хватаясь за голову после отправки приложения в «iTunes Connect») – два.
Таким образом мы будем иметь, например, файл
(
Использование цвета будет выглядеть так:
Предлагаю пойти дальше и написать модель, которая будет представлять различные используемые в приложении цвета:
Для удобства создания можно даже написать
В этом случае константы будут выглядеть так:
В коде цвет задаваться будет таким образом:
И, наконец, для чего могут понадобиться такие дополнительные сложности – это…
Допустим, мы хотим, чтобы наше приложение имело две цветовые схемы: темную и светлую. Для хранения списка цветовых схем определим
В данном случае, думаю, не будет зазорно создать тип для представления модели цветовой схемы в виде «синглтона»:
Я бы его даже определил внутри
Сам
Цветовые константы теперь будут выглядеть уже так:
(
А использование всего этого добра будет выглядеть все так же:
Чтобы поменять цвет какого-то элемента, по прежнему хватит только изменения соответствующей константы. А чтобы добавить еще одну цветовую схему, нужно добавить
Последнее, конечно, можно еще улучшить. Например, если количество схем разрастается, вероятно, удобней будет заменить громоздкий инициализатор на «строителя».
Пожалуй, на этот раз все! Красивого кода!
Все это касается и используемых в приложении цветов.
Последняя редакция – 16 февраля 2019 г.
Цветовые константы
Для начала хочется дать несколько более или менее стандартных рекомендаций.
Во-первых, цвета всех элементов лучше сразу задавать в коде, а не в «Storyboard»/«Interface Builder». Если, конечно, это не приложение с одним экраном с тремя элементами и в одной цветовой схеме. Но даже и в этом случае никогда не знаешь наверняка, как изменится ситуация в будущем.
Во-вторых, все цвета стоит определить константами, вынесенными в отдельный файл. Или в отдельную группу по соседству с соответствующим «view»-кодом.
В-третьих, цвета стоит разделять на категории. Т.е. оперировать не «цветом второй кнопки на первом экране», а чем-нибудь вроде «цвета фона основного типа кнопок».
Если от дизайнера (или от собственного чувства вкуса) поступит сигнал изменить цвет какого-либо элемента, его не придется долго искать – раз, изменять в нескольких местах (забывая какое-то из них и хватаясь за голову после отправки приложения в «iTunes Connect») – два.
Таким образом мы будем иметь, например, файл
Colors.swift
с содержимым вроде:import UIKit
enum ButtonAppearance {
static let backgroundColor = UIColor.white
static let borderColor = UIColor.gray
static let textColor = UIColor.black
}
(
enum
без единого case
– честно говоря, моя любимая структура типа для объявления констант. При таком использользовании, она автоматически лишает нас возможности создавать экземпляры типа и вообще использовать тип как-либо еще кроме задуманного способа.)Использование цвета будет выглядеть так:
let someButton = UIButton()
someButton.backgroundColor = ButtonAppearance.backgroundColor
someButton.layer.borderColor = ButtonAppearance.borderColor.cgColor
Модель цвета
Предлагаю пойти дальше и написать модель, которая будет представлять различные используемые в приложении цвета:
struct SchemeColor {
private let color: UIColor
func uiColor() -> UIColor {
return color
}
func cgColor() -> CGColor {
return color.cgColor
}
}
Для удобства создания можно даже написать
extension
для UIColor
:extension UIColor {
func schemeColor() -> SchemeColor {
return SchemeColor(color: self)
}
}
В этом случае константы будут выглядеть так:
enum ButtonAppearance {
static let backgroundColor = UIColor.white.schemeColor()
static let borderColor = UIColor.gray.schemeColor()
static let textColor = UIColor.black.schemeColor()
}
В коде цвет задаваться будет таким образом:
let someButton = UIButton()
someButton.backgroundColor = ButtonAppearance.backgroundColor.uiColor()
someButton.layer.borderColor = ButtonAppearance.borderColor.cgColor()
И, наконец, для чего могут понадобиться такие дополнительные сложности – это…
Цветовые схемы
Допустим, мы хотим, чтобы наше приложение имело две цветовые схемы: темную и светлую. Для хранения списка цветовых схем определим
enum
:enum ColorSchemeOption {
case dark
case light
}
В данном случае, думаю, не будет зазорно создать тип для представления модели цветовой схемы в виде «синглтона»:
struct ColorScheme {
static let shared = ColorScheme()
private (set) var schemeOption: ColorSchemeOption
private init() {
/*
Здесь должен быть код, который определит цветовую схему и присвоит нужное значение option. Например, загрузив настройки из UserDefaults или взяв значение по умолчанию, если сохраненных настроек нет.
*/
}
}
Я бы его даже определил внутри
SchemeColor
и сделал его private
.Сам
SchemeColor
нужно модифицировать для того, чтобы он был осведомлен о том, какую цветовую схему использует приложение и возвращал нужный цвет:struct SchemeColor {
let dark: UIColor
let light: UIColor
func uiColor() -> UIColor {
return colorWith(scheme: ColorScheme.shared.schemeOption)
}
func cgColor() -> CGColor {
return uiColor().cgColor
}
private func colorWith(scheme: ColorSchemeOption) -> UIColor {
switch scheme {
case .dark: return dark
case .light: return light
}
}
// ColorScheme
}
Цветовые константы теперь будут выглядеть уже так:
enum ButtonAppearance {
static let backgroundColor = SchemeColor(dark: Dark.backgroundColor, light: Light.backgroundColor)
static let borderColor = SchemeColor(dark: Dark.borderColor, light: Light.borderColor)
static let textColor = SchemeColor(dark: Dark.textColor, light: Light.textColor)
private enum Light {
static let backgroundColor = UIColor.white
static let borderColor = UIColor.gray
static let textColor = UIColor.black
}
private enum Dark {
static let backgroundColor = UIColor.lightGray
static let borderColor = UIColor.gray
static let textColor = UIColor.black
}
}
(
extension
для UIColor
, кажется, больше не нужен.)А использование всего этого добра будет выглядеть все так же:
let someButton = UIButton()
someButton.backgroundColor = ButtonAppearance.backgroundColor.uiColor()
someButton.layer.borderColor = ButtonAppearance.borderColor.cgColor()
Чтобы поменять цвет какого-то элемента, по прежнему хватит только изменения соответствующей константы. А чтобы добавить еще одну цветовую схему, нужно добавить
case
в ColorSchemeOption
, набор цветов для этой цветовой схемы в цветовые константы и обновить SchemeColor
.Последнее, конечно, можно еще улучшить. Например, если количество схем разрастается, вероятно, удобней будет заменить громоздкий инициализатор на «строителя».
Пожалуй, на этот раз все! Красивого кода!