37

The Swift documentation says that adding initializers in an extension is possible, and the example in the document is about adding an initializer to a struct. Xcode doesn't recognize UIColor's designated initializer in my convenience initializer:

extension UIColor {
  convenience init(rawValue red: CGFloat, green g: CGFloat, blue b: CGFloat, alpha a: CGFloat) {

    // Can not find out the designated initializer here
    self.init()

  }
}

Any solutions?

Evan R
  • 875
  • 13
  • 27
mrahmiao
  • 1,291
  • 1
  • 10
  • 22

3 Answers3

46

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
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
3

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.

Jordan Smith
  • 10,310
  • 7
  • 68
  • 114
  • Also known as "Swizzeling" – Stijn Dec 22 '16 at 09:40
  • 1
    Alas this will no longer be possible some time in the future. The latest version of Xcode provides a warning that this is deprecated and will be disallowed in future versions of Swift. – Kane Cheshire Apr 29 '17 at 09:58
  • @KaneCheshire please see here for a way to do this in future Swift versions: http://stackoverflow.com/a/42824542/555619 – Jordan Smith Apr 29 '17 at 10:21
  • Yes I've already found that, none of those address the issue that you'll need to call something manually to start the method switching process in future versions of Swift once using `initialize()` is disallowed. – Kane Cheshire Apr 29 '17 at 11:53
1

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.

Abdurrahman Mubeen Ali
  • 1,331
  • 1
  • 13
  • 19