How to add initializers in extensions to existing UIKit classes such as UIColor?
Changing the parameter types will also work.
extension UIColor {
convenience init(red: Int, green: Int, blue: Int, alpha: CGFloat) {
let normalizedRed = CGFloat(red) / 255
let normalizedGreen = CGFloat(green) / 255
let normalizedBlue = CGFloat(blue) / 255
self.init(red: normalizedRed, green: normalizedGreen, blue: normalizedBlue, alpha: alpha)
}
}
Usage:
let newColor: UIColor = UIColor.init(red: 74, green: 74, blue: 74, alpha: 1)
I would usually forget the redundant work of dividing the component values by 255. So I made this method to facilitate me.
Well, if you really, really, really want to override an initialiser, there is a way.
Before you read further: never do this to change UIKit
behaviour. Why? It could confuse the heck out of someone that can't figure out why a UIColor
initialiser isn't doing what it normally does. Only do it to fix a UIKit
bug, or add functionality, etc.
I have used the following to patch several iOS bugs.
Code
extension UIColor {
private static var needsToOverrideInit = true
override open class func initialize() {
// Only run once - otherwise subclasses will call this too. Not obvious.
if needsToOverrideInit {
let defaultInit = class_getInstanceMethod(UIColor.self, #selector(UIColor.init(red:green:blue:alpha:)))
let ourInit = class_getInstanceMethod(UIViewController.self, #selector(UIColor.init(_red:_green:_blue:_alpha:)))
method_exchangeImplementations(defaultInit, ourInit)
needsToOverrideInit = false
}
}
convenience init(_red: CGFloat, _green: CGFloat, _blue: CGFloat, _alpha: CGFloat) {
// This is trippy. We swapped implementations... won't recurse.
self.init(red: _red, green: _green, blue: _blue, alpha: _alpha)
///////////////////////////
// Add custom logic here //
///////////////////////////
}
}
Explanation
This is using the dynamic nature of Objective-C, called from Swift, to swap method definition pointers at runtime. If you don't know what this means, or implications of it, it is probably a good idea to read up on the topic before you use this code.
You can't do it like this, you have to chose different parameter names to create your own initializers/ You can also make then generic to accept any BinaryInteger or BinaryFloatingPoint types:
extension UIColor {
convenience init<T: BinaryInteger>(r: T, g: T, b: T, a: T = 255) {
self.init(red: .init(r)/255, green: .init(g)/255, blue: .init(b)/255, alpha: .init(a)/255)
}
convenience init<T: BinaryFloatingPoint>(r: T, g: T, b: T, a: T = 1.0) {
self.init(red: .init(r), green: .init(g), blue: .init(b), alpha: .init(a))
}
}
let green1 = UIColor(r: 0, g: 255, b: 0, a: 255) // r 0,0 g 1,0 b 0,0 a 1,0
let green2 = UIColor(r: 0, g: 1.0, b: 0, a: 1.0) // r 0,0 g 1,0 b 0,0 a 1,0
let red1 = UIColor(r: 255, g: 0, b: 0) // r 1,0 g 0,0 b 0,0 a 1,0
let red2 = UIColor(r: 1.0, g: 0, b: 0) // r 1,0 g 0,0 b 0,0 a 1,0