Extending UIColor

After writing my previous article: “Subscripting Shortcuts” I tried to come up with a good usage of string subscripts in iOS, and decided a good example was to boost the power of UIColor by adding a couple of initializers that catered for two more widespread colour notations that are more common in the design and web industries: 255-RGB and HEX.

In this tutorial we will make use of our favourite tool, categories, to extend the base power of the framework’s UIColor class and allow for new ways of creating colours. Bear in mind that these initializers are geared towards speeding up development processes in sacrifice of  performance, specially when it comes to communications between developers and the design team. They will inherently produce a very small performance overhead on any app that adopts them (specially the hex conversion, as we will be doing string parsing and string parsing is ALWAYS expensive). Having said that, unless we iterate though a huge amount of colour objects, we can safely consider the knock-on effect as dismissible.

So then, full speed ahead to our new notations →

255-RGB

This one is blatantly simple and doesn’t really need much explanation. We just create a UIColor extension with the following code in it:


private let MAX_255_COLOR : CGFloat = 255.0

extension UIColor {

  convenience init(red255 red:CGFloat, green255 green:CGFloat, blue255 blue:CGFloat, alpha:CGFloat) {
    assert(red >= 0 && red <= 255, "invalid red value passed, value must be between 0-255, got: \(red)")
    assert(green >= 0 && green <= 255, "invalid green value passed, value must be between 0-255, got: \(green)")
    assert(blue >= 0 && blue <= 255,"invalid blue value passed, value must be between 0-255, got: \(blue)")
    self.init(red: red/MAX_255_COLOR, green: green/MAX_255_COLOR, blue: blue/MAX_255_COLOR, alpha: alpha)
  }
}

Take note of the usage of a private external variable before the extension’s declaration. You can think of this as Obj-C’s static const variable equivalent with an added bonus: they are lazy by default, which means that unless they get used they never get instantiated and do not add to the application’s memory footprint (sweet!).

After that our convenience initializer is pretty straightforward, we assert that the input values we receive are within the correct range and if not, we create an appropriate assertion. This “always check your variables” approach is general good practice and helps heaps in avoiding mistakes that may happen down the line, produced by either yourself or other developers that mistakenly use your method. There is something to be said about having a good explanation log for the assertion, too, so make sure you put any relevant information on it.

That’s it for 255RGB, now for something considerably more challenging:

HEX

The thing we have to bear in mind is that hex can come in various notations depending on slight differences in usage. These differences are mainly preferences for the developer and depend mainly on their background. We will need to take this into account when writing our colour parser to make our extension more usable. Note that the following are all the same colour (a twitter-ish blue):

  • A3CCEF
  • #a3ccef
  • 0xA3CceF
  • 0XA3CCEF

For the sake of a challenge, we also want to take into account 8 character notations to cater for alpha components, so our RGB code can also append some data making it a RGBA code. The following strings should then add a bit of transparency to our blues:

  • A3CCEF5A
  • #a3ccef5a
  • 0xA3CceF5a
  • 0XA3CCEF5A

Out of interest, we might also want to add some shortcuts to deal with grey-scales, where instead of typing colour names like: A3A3A3 I can just pass any of the following 2 character notations and the parser will be smart enough to interpret their meanings:

  • A3
  • #a3
  • 0xa3
  • 0XA3

And just for the sake of completeness, we’ll add a 4 character notation to our parser for dealing with grey-scales with alpha, so the following string can be made as valid inputs as well:

  • A35A
  • #a35a
  • 0xa35A
  • 0XA35A

The first challenge, then, is to build a parser that can accept hex strings of 2, 4, 6 and 8 characters, which might or not be appended with either “#”, “0x” or “0X” notations, and from  these string can get the equivalent 255-RGB values.

Because of the highly specific solution needed for this, and because we would really only use this solution naturally from within our UIColor category, we can decide on creating a private class that will provide the utility functions for transforming our strings to 255-RGB values. The reason for this is that having a private class ensures that the code will only ever be used by our extension whilst also providing a completely separate logic for sections of the code that are not entirely related to the expected behaviour of a UIColor object. The rule is order, always!

Let’s call this class: “EPICHexStringScanner”:


private let MAX_255_COLOR : CGFloat = 255.0

extension UIColor {
  //UIColor specific code goes here
}

class EPICHexStringScanner {
  //our scanning logic will go here
}

Now that we have the perfect home for our scanning functions. Lets start with the easiest one: 2 character notations.


class func scanHex2(hexString:String, inout red:UInt32, inout green:UInt32, inout blue:UInt32) {
  let scanner = NSScanner(string: hexString)
  if (!scanner.scanHexInt(&red)) {
    assertionFailure("passed hex value is invalid, hex can only contain valid values with 2 characters and must be composed by characters 0-9, a-z or A-Z. Got: \(hexString)")
  }
  green = red
  blue = red
}

In order to keep our functions simple, we’re making the assumptions that all input strings will be clean hexes without any notations (calm down… we will take care of cleaning up input strings before we call these methods, remember the class is private!). From then we leverage the use of NSScanner’s scanHexInt function to assign the right values to passed inout Unsigned Int values (by default scanHexInt will return a 255 notation). As in our 255-RGB implementation, we always check our logic with assertions in the right places, and create the proper assertion failure when the function is not being used properly. Remember, there is no such thing as too much description for your errors!

As you can see the solution was fairly straightforward since our hex string did not need to be broken down to fetch the colour values. But if we want to add support for strings with more characters, like 4 character notations, we will (finally) leverage the use of our String subscripts category developed in “Subscripting Shortcuts“:


class func scanHex4(hexString:String, inout red:UInt32, inout green:UInt32, inout blue:UInt32, inout alpha:UInt32) {
  if !NSScanner(string: hexString[0..<2]).scanHexInt(&red) ||
     !NSScanner(string: hexString[2..<4]).scanHexInt(&alpha) {
    assertionFailure("passed hex value is invalid, hex can only contain valid values with 4 characters and must be composed by characters 0-9, a-z or A-Z. Got: \(hexString)")
  }
  green = red
  blue = red
}

As you can see, we’re using our shortcut notation for breaking down our hex string into specific bits that can be fed to NSScanner, allowing us to separate the string components for our grey scale value and our alpha value. Having implemented this, our solutions for hex 6 and hex 8 notations become very straightforward:


class func scanHex6(hexString:String, inout red:UInt32, inout green:UInt32, inout blue:UInt32) {
  if !NSScanner(string: hexString[0..<2]).scanHexInt(&red) ||
     !NSScanner(string: hexString[2..<4]).scanHexInt(&green) ||
     !NSScanner(string: hexString[4..<6]).scanHexInt(&blue) {
    assertionFailure("passed hex value is invalid, hex can only contain valid values with 6 characters and must be composed by characters 0-9, a-z or A-Z. Got: \(hexString)")
  }
}

class func scanHex8(hexString:String, inout red:UInt32, inout green:UInt32, inout blue:UInt32, inout alpha:UInt32) {
  if !NSScanner(string: hexString[0..<2]).scanHexInt(&red) ||
     !NSScanner(string: hexString[2..<4]).scanHexInt(&green) ||
     !NSScanner(string: hexString[4..<6]).scanHexInt(&blue) ||
     !NSScanner(string: hexString[6..<;8]).scanHexInt(&alpha) {
    assertionFailure("passed hex value is invalid, hex can only contain valid values with 8 characters and must be composed by characters 0-9, a-z or A-Z. Got: \(hexString)")
  }
}

Now all that’s left is to tie this all together with one publicly exposed method to rule them all, in charge of cleaning up our input strings and creating our UIColor objects:


convenience init(var hexString:String) {
  hexString = hexString.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil).stringByReplacingOccurrencesOfString("0x", withString: "", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)

  var alpha : UInt32 = UInt32(MAX_255_COLOR)
  var red = alpha
  var green = alpha
  var blue = alpha

  let hexLength = hexString.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) //we ignore other string encodings as emoticons and composed character sequence are not valid elements in a hexstring to start with.
  switch hexLength {
  case 2:
    EPICHexStringScanner.scanHex2(hexString, red: &red, green: &green, blue: &blue);
  case 4:
    EPICHexStringScanner.scanHex4(hexString, red: &red, green: &green, blue: &blue, alpha: &alpha);
  case 6:
    EPICHexStringScanner.scanHex6(hexString, red: &red, green: &green, blue: &blue);
  case 8:
    EPICHexStringScanner.scanHex8(hexString, red: &red, green: &green, blue: &blue, alpha: &alpha);
  default:
    assertionFailure("passed hex value is invalid, hex can only contain valid values with 2 (white), 4 (white + alpha), 6 (rgb) or 8 (rgb + alpha) characters and must be composed by characters 0-9, a-z and A-Z. Got: \(hexString)")
  }

  self.init(red255: CGFloat(red), green255: CGFloat(green), blue255: CGFloat(blue), alpha: CGFloat(alpha)/MAX_255_COLOR)
}

Great stuff! Now we can create UIColor objects with the following notations:

//255-RGB
UIColor(red255: 120, green255: 51, blue255: 86, alpha: 0.5)

//HEX
UIColor(hexString: "FA")
UIColor(hexString: "#6F2A")
UIColor(hexString: "0x22ac4d")
UIColor(hexString: "0X89cc24ab")

Hopefully this tutorial will give you some insight on how to leverage the power of subscripting in Swift and hopefully will have helped you create a useful UIColor category for your projects. Feel free to download the full code for this tutorial (with tests) from my EPICColors page. Happy Coding!

Author: Danny Bravo

Director @ EPIC