Pull to refresh

Способ управления цветовыми схемами «Swift» «iOS»-приложения

Reading time4 min
Views10K
Даже для самого что ни на есть начинающего разработчика (скорее, на которого и рассчитан данный очерк), надеюсь, не секрет, что в коде не должно присутствовать никаких т.н. «hardcoded»-значений и прочих всяких там «magic numbers». Почему – тоже, надеюсь, понятно, а если нет, то в Сети имеются десятки, а то и сотни статей на эту тему, а также написан классический труд. «Android Studio» (наверное, не во всех случаях, но все же) даже любит генерировать «warnings» на эту тему и предлагать выносить строки и т.д. в ресурсные файлы. «Xcode» (пока?) такими подсказками нас не балует, и разработчику приходится самостоятельно держать себя в узде или, скажем, получать по рукам от коллег после «code review».

Все это касается и используемых в приложении цветов.



Последняя редакция – 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.

Последнее, конечно, можно еще улучшить. Например, если количество схем разрастается, вероятно, удобней будет заменить громоздкий инициализатор на «строителя».

Пожалуй, на этот раз все! Красивого кода!
Tags:
Hubs:
+11
Comments13

Articles

Change theme settings