📜 ⬆️ ⬇️

The way to manage color schemes "Swift" "iOS" applications

Even for the novice developer himself (rather, this essay is intended), I hope it is not a secret that there should not be any so-called in the code. "Hardcoded" values ​​and all sorts of "magic numbers" there . Why - also, I hope, is understandable, and if not, then there are dozens or even hundreds of articles on the web, as well as a classic work . Android Studio (probably not in all cases, but still) even likes to generate warnings on this topic and offer to make lines, etc. into resource files . “Xcode” (for now?) Doesn’t indulge us with such hints, and the developer has to keep himself in check or, say, get his hands on from colleagues after “code review” .

All this applies to the colors used in the application.


')
Last edited February 16, 2019


Color constants



For starters, I want to give some more or less standard recommendations.

First, it is better to set the colors of all elements immediately in the code, and not in the “Storyboard” / “Interface Builder” . Unless, of course, this is not a single-screen application with three elements and one color scheme. But even in this case, you never know for sure how the situation will change in the future.

Secondly, all colors should be defined by constants in a separate file. Or in a separate group next to the corresponding "view" code.

Thirdly, colors should be divided into categories. Those. operate not with the “color of the second button on the first screen”, but with something like “the background color of the main type of buttons”.

If the designer (or his own sense of taste) receives a signal to change the color of an element, it does not have to be searched for long - once, changed in several places (forgetting one of them and clutching his head after sending the application to iTunes Connect ) - two.

Thus, we will have, for example, a Colors.swift file with contents like:

 import UIKit enum ButtonAppearance { static let backgroundColor = UIColor.white static let borderColor = UIColor.gray static let textColor = UIColor.black } 


( enum without a single case - frankly, my favorite type structure for declaring constants. With this use, it automatically prevents us from creating instances of the type and generally using the type in any way other than the intended method.)

Using color will look like this:

 let someButton = UIButton() someButton.backgroundColor = ButtonAppearance.backgroundColor someButton.layer.borderColor = ButtonAppearance.borderColor.cgColor 


Color model



I propose to go ahead and write a model that will represent the different colors used in the application:

 struct SchemeColor { private let color: UIColor func uiColor() -> UIColor { return color } func cgColor() -> CGColor { return color.cgColor } } 


For ease of creation, you can even write an extension for UIColor :

 extension UIColor { func schemeColor() -> SchemeColor { return SchemeColor(color: self) } } 


In this case, the constants will look like this:

 enum ButtonAppearance { static let backgroundColor = UIColor.white.schemeColor() static let borderColor = UIColor.gray.schemeColor() static let textColor = UIColor.black.schemeColor() } 


In the code, the color will be set as follows:

 let someButton = UIButton() someButton.backgroundColor = ButtonAppearance.backgroundColor.uiColor() someButton.layer.borderColor = ButtonAppearance.borderColor.cgColor() 


And, finally, why such additional difficulties may be needed - this ...

Color schemes



Suppose we want our application to have two color schemes: dark and light. To store a list of color schemes, we define enum :

 enum ColorSchemeOption { case dark case light } 


In this case, I think, it will not be embarrassing to create a type for representing the color scheme model in the form of a “singleton” :

 struct ColorScheme { static let shared = ColorScheme() private (set) var schemeOption: ColorSchemeOption private init() { /*    ,         option. ,    UserDefaults     ,    . */ } } 


I would even define it inside SchemeColor and make it private .

SchemeColor itself SchemeColor to be modified in order for it to be aware of which color scheme the application uses and return the desired color:

 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 } 

Color constants will now look like this:

 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 } } 


(The extension for UIColor does not seem to be needed anymore.)

And the use of all this stuff will look the same:

 let someButton = UIButton() someButton.backgroundColor = ButtonAppearance.backgroundColor.uiColor() someButton.layer.borderColor = ButtonAppearance.borderColor.cgColor() 


To change the color of an element, the changes of the corresponding constant are still enough. And to add another color scheme, you need to add a case to ColorSchemeOption , a set of colors for this color scheme in color constants and update SchemeColor .

The latter, of course, can still be improved. For example, if the number of circuits grows, it will probably be more convenient to replace the bulky initializer with a “builder” .

Perhaps this time all! Beautiful code!

Source: https://habr.com/ru/post/349348/


All Articles