I've been trying to implement a theme logic for my custom components. I'll use ZFButton as example.
When the app starts I instantiate ZFbutton and set whatever characteristics I want this one theme to have:
let theme = ZFButton()
theme.backgroundColor = .red //UIButton property
theme.cornerRadius = 8 //ZFButton property
theme.borderColor = .green //ZFButton property
theme.borderWidth = 1 //ZFButton property
Then add it to the theme array:
ZFButton.themes.append(theme)
which resides in the ZFButton as follows:
public static var themes = [ZFButton]()
In my ZFButton I have the following property, which allows me to choose from the IB Attributes Inspector which theme I want to use for that particular ZFButton
@IBInspectable public var theme: Int = 0 { didSet { self.setupTheme() } }
And finally, once the theme property is set, setupTheme() is called, in which I attempt to copy the value set from all properties of a given theme to this particular instance of ZFButton. For that I use reflection:
private func setupTheme() {
if ZFButton.themes.count > self.theme {
let theme = ZFButton.themes[self.theme]
let mirror = Mirror(reflecting: theme)
for child in mirror.children {
if let label = child.label,
label != "theme", //check to prevent recursive calls
self.responds(to: Selector(label)) {
self.setValue(child.value, forKey: label)
print("Property name:", child.label)
print("Property value:", child.value)
}
}
}
}
Now I have two problems:
1 - Properties that have setter/getter do not show up in the reflection, example:
@IBInspectable public var borderColor: UIColor {
set { layer.borderColor = newValue.cgColor }
get { return UIColor(cgColor: layer.borderColor!) }
}
Whereas properties that use didSet do, such as:
@IBInspectable public var iconText: String = "" { didSet { self.setupIcon() } }
However, I do need a getter here to return the borderColor
in layer
.
2 - When using Mirror to reflect all ZFButton properties, besides the issue described in (1), I don't get UIButton properties either, is there a way to get ZFButton's superclass (UIButton) properties as well?
For the first problem I ended up using the following extension, which does see properties with getters/setters unlike Mirror:
extension NSObject {
func propertiesNames() -> [String] {
var count : UInt32 = 0
let classToInspect = type(of: self)
guard let properties : UnsafeMutablePointer <objc_property_t> = class_copyPropertyList(classToInspect, &count) else { return [] }
var propertyNames : [String] = []
let intCount = Int(count)
for i in 0..<intCount {
let property : objc_property_t = properties[i]
let propertyName = NSString(utf8String: property_getName(property))!
propertyNames.append(propertyName as String)
}
free(properties)
return propertyNames
}
}
As for the second issue I ended up copying each property over from the theme to the button as they are always the same. The goal was to avoid having to maintain a Theme class to bridge values every time something new is implemented in ZFButton.