Skip to content

Instantly share code, notes, and snippets.

@rpomeroy
Created July 20, 2014 20:04
Show Gist options
  • Save rpomeroy/c0bf58a2c62f34fdad8d to your computer and use it in GitHub Desktop.
Save rpomeroy/c0bf58a2c62f34fdad8d to your computer and use it in GitHub Desktop.
NSColor from hex or hexString
// Paste in playground
// Handy NSColor and String extensions
import Cocoa
extension String {
func conformsTo(pattern: String) -> Bool {
let pattern = NSPredicate(format:"SELF MATCHES %@", pattern)
return pattern.evaluateWithObject(self)
}
}
extension NSColor {
class func fromHex(hex: Int, alpha: Float) -> NSColor {
let red = CGFloat((hex & 0xFF0000) >> 16) / 255.0
let green = CGFloat((hex & 0xFF00) >> 8) / 255.0
let blue = CGFloat((hex & 0xFF)) / 255.0
return NSColor(calibratedRed: red, green: green, blue: blue, alpha: 1.0)
}
class func fromHexString(hex: String, alpha: Float) -> NSColor? {
// Handle two types of literals: 0x and # prefixed
var cleanedString = ""
if hex.hasPrefix("0x") {
cleanedString = hex.substringFromIndex(2)
} else if hex.hasPrefix("#") {
cleanedString = hex.substringFromIndex(1)
}
// Ensure it only contains valid hex characters 0
let validHexPattern = "[a-fA-F0-9]+"
if cleanedString.conformsTo(validHexPattern) {
var theInt: UInt32 = 0
let scanner = NSScanner.scannerWithString(cleanedString)
scanner.scanHexInt(&theInt)
let red = CGFloat((theInt & 0xFF0000) >> 16) / 255.0
let green = CGFloat((theInt & 0xFF00) >> 8) / 255.0
let blue = CGFloat((theInt & 0xFF)) / 255.0
return NSColor(calibratedRed: red, green: green, blue: blue, alpha: 1.0)
} else {
return Optional.None
}
}
}
// Tests
let hex: Int = 0xa1bbca
let hexString: String = "#a1bbca"
let hexStringAlt = "0xa1bbca"
let hexColor = NSColor.fromHex(hex, alpha: 1.0)
let hexColorFromString = NSColor.fromHexString(hexString, alpha: 1.0)!
let hexColorFromStringAlt = NSColor.fromHexString(hexStringAlt, alpha: 1.0)!
@ozanm
Copy link

ozanm commented Jan 10, 2021

Thank you for creating this, it really saved me a lot of time. If one were to need it, the following is the Swift 5 version of your code.

import Cocoa

extension String  {
    func conformsTo(_ pattern: String) -> Bool {
        return NSPredicate(format:"SELF MATCHES %@", pattern).evaluate(with: self)
    }
}

extension NSColor {
    convenience init(hex: Int, alpha: Float) {
        self.init(
            calibratedRed: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((hex & 0xFF00) >> 8) / 255.0,
            blue: CGFloat((hex & 0xFF)) / 255.0,
            alpha: 1.0
        )
    }
    
    convenience init(hex: String, alpha: Float) {
        // Handle two types of literals: 0x and # prefixed
        var cleanedString = ""
        if hex.hasPrefix("0x") {
            cleanedString = String(hex[hex.index(cleanedString.startIndex, offsetBy: 2)...hex.endIndex])
        } else if hex.hasPrefix("#") {
            cleanedString = String(hex[hex.index(cleanedString.startIndex, offsetBy: 1)...hex.endIndex])
        }
        
        // Ensure it only contains valid hex characters 0
        let validHexPattern = "[a-fA-F0-9]+"
        if cleanedString.conformsTo(validHexPattern) {
            var value: UInt32 = 0
            Scanner(string: cleanedString).scanHexInt32(&value)
            self.init(hex: Int(value), alpha: 1)
        } else {
            fatalError("Unable to parse color?")
        }
    }
}

@sptndc
Copy link

sptndc commented Feb 1, 2023

Thanks @ozanm this is the fix for "Fatal Error: String index out of bounds" in Swift 5:

import Cocoa

extension String  {
    func conformsTo(_ pattern: String) -> Bool {
        return NSPredicate(format:"SELF MATCHES %@", pattern).evaluate(with: self)
    }
}

extension NSColor {
    convenience init(hex: Int, alpha: Float) {
        self.init(
            calibratedRed: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((hex & 0xFF00) >> 8) / 255.0,
            blue: CGFloat((hex & 0xFF)) / 255.0,
            alpha: 1.0
        )
    }
    
    convenience init(hex: String, alpha: Float) {
        // Handle two types of literals: 0x and # prefixed
        var cleanedString = ""
        if hex.hasPrefix("0x") {
            cleanedString = String(hex[hex.index(cleanedString.startIndex, offsetBy: 2)..<hex.endIndex])
        } else if hex.hasPrefix("#") {
            cleanedString = String(hex[hex.index(cleanedString.startIndex, offsetBy: 1)..<hex.endIndex])
        }
        
        // Ensure it only contains valid hex characters 0
        let validHexPattern = "[a-fA-F0-9]+"
        if cleanedString.conformsTo(validHexPattern) {
            var value: UInt32 = 0
            Scanner(string: cleanedString).scanHexInt32(&value)
            self.init(hex: Int(value), alpha: 1)
        } else {
            fatalError("Unable to parse color?")
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment