如何在扩展中为现有的UIKit类(例如UIColor)添加初始化器?

39

Swift文档表示在扩展中添加初始化程序是可能的,文件中的示例是关于向结构体添加初始化器。我的方便初始化程序中Xcode无法识别UIColor的指定初始化程序:

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()

  }
}

有什么解决方案吗?

3个回答

48
你不能这样做,你必须选择不同的参数名称来创建自己的初始化器。你也可以使它们成为通用的,以接受任何BinaryInteger或BinaryFloatingPoint类型。
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

1
为什么不用a:而是用opacity:呢?:p - nacho4d

3

如果您真的非常想要覆盖一个初始化程序,有一种方法可以做到。

在继续阅读之前:永远不要这样做来更改UIKit的行为。为什么?它可能会让那些无法弄清楚为什么UIColor初始化程序没有像平常一样工作的人感到困惑。只应该用于修复UIKit中的错误或添加功能等方面。

我已经使用以下内容修补了几个iOS bug。

代码

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

    }

}

说明

这是利用Objective-C的动态特性,在Swift中调用,以在运行时交换方法定义指针。如果您不知道这意味着什么,或者它的影响,使用此代码之前最好先了解一下这个主题。


也被称为“Swizzeling”。 - Stijn
1
可惜的是,这在不久的将来将不再可能。Xcode的最新版本会提供警告,说明这已经被弃用,并且在Swift的未来版本中将被禁止使用。 - Kane Cheshire
@KaneCheshire 请参考这里,了解在未来的Swift版本中实现此功能的方法:https://dev59.com/r1gQ5IYBdhLWcg3wZDGd#42824542 - Jordan Smith
是的,我已经发现了,这些都没有解决问题,因为在未来版本的Swift中,一旦禁止使用initialize(),您将需要手动调用某些内容来启动方法切换过程。 - Kane Cheshire

1
更改参数类型也可以起作用。
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)
    }
}

使用方法:

let newColor: UIColor = UIColor.init(red: 74, green: 74, blue: 74, alpha: 1)

我通常会忘记将组件值除以255的冗余工作。因此,我编写了这个方法来方便自己。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接