4

I have custom colors within my code. I use them several times and I would like to have them allocated only once.

The situation / problem

If we get a look at UIColor headers we can see the following :

[...]

// Some convenience methods to create colors.  These colors will be as calibrated as possible.
// These colors are cached.
  
open class var black: UIColor { get } // 0.0 white

open class var darkGray: UIColor { get } // 0.333 white

[...]

I've created an extension of UIColor, like so :

import UIKit

extension UIColor {
    
    class func colorWithHexString(_ hex: String) -> UIColor {
        
        print("\(#function): \(hex)")
        
        // some code, then it return a UIColor

        return UIColor(
            red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )

    }
    
    // Option A
    open class var myColorOne : UIColor {
        get {
            return colorWithHexString("AABBCC")
        }
    }
    
    // Option B
    class func myColorTwo() -> UIColor {
        return colorWithHexString("DDEEFF")
    }
}

From there I can use my colors easily, with either having a variable or a function.

// A
UIColor.myColorOne

// B
UIColor.myColorTwo()

Sadly, I'm not fully happy with that. Indeed, every time I want to use those colors : a new UIColor allocation is made.

What I've tried

Apple managed to make their color cached apparently. I would like to do so myself too. I've tried several things but none seems to be ideal.

1 - Using dispatch_once ✗

As visible on Swift page : the free function dispatch_once is no longer available in Swift.

2 - Creating a constant (let) ✗

I get the following error : extensions may not contain stored properties

3 - Creating a singleton ~

It does work (each color are created only once) with the following

import UIKit

class Colors : UIColor {
    
    // Singleton
    static let sharedInstance = Colors()
    
    let myColorOne : UIColor = {
        return UIColor.colorWithHexString("AABBCC")
    }()
    
    let myColorTwo : UIColor = {
        return UIColor.colorWithHexString("DDEEFF")
    }()
    
}

But it forces me to have one more file and call my colors like so

Colors.sharedInstance.myColorOne

Isn't there any way to get the colors I want like that UIColor.myColorOne and have them cached like Apple does ?

Community
  • 1
  • 1
lchamp
  • 6,592
  • 2
  • 19
  • 27
  • @Grimxn, aren't global variable (even lazily initialized) a *bad practice/idea* ? Also, it won't be call like other colors `UIColor.white`. How did Apple wrote theirs... Might need to store the color after first initialization and fetch it on later calls (not sure about performance here, versus the singleton). – lchamp Feb 08 '17 at 21:45
  • 1
    ... ... sorry! – Grimxn Feb 08 '17 at 22:30

4 Answers4

6

You can use the same approach as in Using a dispatch_once singleton model in Swift, i.e. static constant stored properties which are initialized lazily (and only once). These can be defined directly in the UIColor extension:

extension UIColor {
    convenience init(hex: String) {
        // ...          
    }

    static let myColorOne = UIColor(hex:"AABBCC")
    static let myColorTwo = UIColor(hex:"DDEEFF")
}
Carien van Zyl
  • 2,853
  • 22
  • 30
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
2

There might be a better way to do it, but using a global variable (like Grimxn mentioned in the comment) is one way to solve the problem.

Below is an example you can copy & paste into a playground:

import UIKit

extension UIColor {

    class func colorWithHexString(_ hex: String) -> UIColor {
        print("allocate color")
        // do you conversion here...
        return UIColor.black
    }

}

private let myPrivateColorOne = UIColor.colorWithHexString("#ffffff")

extension UIColor {

    open class var myColorOne: UIColor {
        get {
            print("get color")
            return myPrivateColorOne
        }
    }

}

UIColor.myColorOne
UIColor.myColorOne

When you execute the code, the getter will be called twice, but colorWithHexString only once.

Janek
  • 56
  • 4
1

You could use your singleton to store the generated UIColor values as above and solve the Colors.sharedInstance.myColorOne naming problem by extending UIColor and putting the access in there:

extension UIColor {
    class func myColorTwo() -> UIColor {
        return Colors.sharedInstance.myColorTwo
    }
}
simonWasHere
  • 1,307
  • 11
  • 13
0

In Swift 3:

extension UIColor {
    class func color(hexString: String) -> UIColor {
        // ...
    }

    static let myColorOne = color(hexString: "AABBCC")
}
Joshua Kaden
  • 1,210
  • 11
  • 16