Add styles to your UIKit components
G8 (italian: dʒɔtto)
G8 allow to centralize themes and apply styles to your interface and your UIKit objects. Define uniform styles for all your labels, buttons and other views and apply them when you need.
Feature
- Define style in a clear and neat way
- Mantain your palettes and base styles in a centrilized place: modify once to update them all!
- Fast and simple reuse of common styles
- Apply or change theme with a single line of code
- Guided way to find customizable UIKit properties, thanks to predefined enums
- Almost all UIKit views managed
Installation
G8 is available through Swift Package Manager
Setup
I suggest you to arrange color, font and dimension palettes using one or more nested enums containing static lets. That way, it will be easier to modify your palettes updating all the app theme in one shot, without the use of troublesome and dangerous literals all over.
Example:
enum ThemeConstants {
enum Colors {
static let brand = UIColor(red: 0.4, green: 0.7, blue: 0.1, alpha: 1)
static let commonText = UIColor(named: "CommonText") // from assets
static let background = UIColor(white: 0.9, alpha: 1)
}
enum FontNames {
static let regular = "HelveticaNeue-Light"
static let bold = "HelveticaNeue-Bold"
}
enum Fonts {
static let common = UIFont(name: FontNames.regular, size: 16)
static let commonBold = UIFont(name: FontNames.bold, size: 16)
}
}
Now you can start defining your styles.
G8Style
A G8Style
style is basically a struct
- who wraps a [String: Any?]
dictionary - and can be defined as a dictionary itself.
Every value
in the dictionary contains another G8Style
or a specific value to assign (e.g. a color, a font, ...).
Every key
in the dictionary must contain a String
pointing to:
- a UIKit object if
value
is another style - a property of the UIKit object if
value
contains a value to set
The key
can always be a string keypath. G8 provides the keys to access all managed properties for UIKit views through static enums, in order to limit the use of literals.
Here an example:
static let viewController: G8Style = [ // 1.
"label1.textColor": UIColor.red, // 2.
"label2": boldLabel, // 3.
"label3": commonLabelColored,
"label4": [ // 4.
G8K.Label.font: UIFont.italicSystemFont(ofSize: 30), // 5.
G8K.Label.textColor: UIColor.darkGray,
G8K.Label.textAlignment: NSTextAlignment.right,
G8K.Label.lineBreakMode: NSLineBreakMode.byTruncatingHead
],
"colorView": [
G8K.View.backgroundColor: ThemeConstants.Colors.background // 6.
],
...
]
- new style instance
- there must be a property named
label1
with a property namedtextColor
on the object to whom the styleviewController
will be applied, and that property should be ofUIColor
type and will be set to UIColor.red - a style named
boldLabel
will be applied to propertylabel2
- this is a inline defined style - use inline styles only if you don't intend to reuse them
- accessing to property
font
oflabel4
- aUILabel
- using predefined G8 keys contained in theG8K
enum, alias ofG8.Keys
- the value applied is from the constants palette enum
How to apply a style
It's very easy to apply a G8Style
style to any object: starting from a G8 instance - can be a global one - you can call the function
func applyStyle(_ style: G8Style, to object: Any)
passing the style and the object.
var g8 = G8()
g8.applyStyle(DefaultTheme.viewController, to: self)
You can of course define the style inline, without using any stored style.
Logging
The G8 instance provides a logger: it allows to write on the XCode console infos and errors, to help you debug issues.
Available loggin levels are:
error
: G8 write only errors, as if you are trying to write a property using a wrong type (writing a font on a backgroundColor)warning
: G8 also writes warnings, like a property who can't be found in the current objectverbose
: G8 writes everything it does: every value applied to a property or every traversed keypath
The default logging level is warning
. Logging works calling the print
function.
Custom G8ValueApplier
s
G8 works using a bunch of valueAppliers: it crosses the kaypath string - if any - getting the final object - label1
in the point 2 of the example code above - and it tries to set the property.
Every time you try to apply a value (not a style) to a property, G8 gets its type and all of super-types and searches a valueApplier for these types. If any, it searches for a property to set in that valueApplier.
For example, if you have a UILabel
, the property will be searched in the UILabel
valueApplier and in the UIView
valueApplier too.
You can define a new custom G8ValueApplier
and register it on your G8 instance the manage your custom views and widgets.
The G8ValueApplier
struct requires a generic type T
- the valueApplier managed type - and is instantiated defining the closure that is called to apply a property value on the T
-type objects. Here's what the closure gives you:
value
: the value to be applied to the propertyobject
: the object of typeT
keyString
: the name of the property to be set, asString
key
: the property to be set, asG8Key
- a inner G8 type, can be useful in some specific cases
Tipically, in the closure you may want to do a switch
on the keyString
value and call the function
applyValue<T, O: AnyObject>(_ value: Any, to object: O, at keyPath: ReferenceWritableKeyPath<O, T>)
to convert the keyString
string in a KeyPath
of object
to set value
.
The closure returns a Bool
indicating if the value has been managed by the current valueApplier or not. This is useful for logging purposes.
You can find many examples of G8ValueApplier
s in the G8ValueApplier
struct.
Here an example:
public static let themeableLabel = G8ValueApplier<UILabel> { (value, object, keyString, key) in
switch keyString {
case G8K.Label.textColor.stringValue:
applyValue(value, to: object, at: \.textColor)
case G8K.Label.font.stringValue:
applyValue(value, to: object, at: \.font)
case G8K.Label.textAlignment.stringValue:
applyValue(value, to: object, at: \.textAlignment)
case G8K.Label.lineBreakMode.stringValue:
applyValue(value, to: object, at: \.lineBreakMode)
default:
return false
}
return true
}
Finally, you have to register the newly defined valueApplier to your G8 instance with either the add(valueApplier: ValueApplier)
of the add(valueAppliers: [ValueApplier])
function:
var g8 = G8()
g8.add(valueApplier: themeableLabel)