LoginSignup
13
10

More than 5 years have passed since last update.

UIColorと16進整数・カラーコード文字列との相互変換

Last updated at Posted at 2016-06-06

ごめんなさい

ここでRGBAの値をIntで扱っていますが、
RGBAの値はIntだとオーバーフローしてしまうことに気づいてしまいました

UInt32に置き換えれば動くのですが
キャスト処理などをこの記事のソースコード全体に行わなければなりません

またどこかで修正します・・・
(2016.07.1 加筆)

まえがき

役に立つかどうかよくわからないTipsシリーズ
今回少し長めです

色を扱うクラスは UIColor です
これはこれで標準でも便利なクラスですが
もっとお手軽に使えるように色々と拡張してみます

下ごしらえ

UIColor の便利な拡張のために
まずはそれ用のファイルを作ります
UIColor+Utility.swiftとでも名づけます

そして、まずファイル内だけで使うプライベートな定義します

UIColor+Utility.swift
extension UIColor {

    private static let maxHex = 0xFFFFFFFF
    private static let minHex = 0x0

    private static let prefix = "#"

    private class func substring(string: String, location: Int, length: Int? = nil) -> String {
        let strlen = string.characters.count
        var len = length ?? strlen
        if location + len > strlen {
            len = strlen - location
        }
        return (string as NSString).substringWithRange(NSMakeRange(location, len))
    }

    private class func intByCGFloat(v: CGFloat) -> Int {
        return Int(round(v * 255.0))
    }

    private class func hexStringByCGFloat(v: CGFloat) -> String {
        let n = self.intByCGFloat(v)
        return NSString(format: "%02X", n) as String
    }
}

substringメソッドは
文字列操作でよくある指定箇所の取得です
むしろ String の extension として別で定義してもいいかもしれませんが、
今回はこのファイルだけで完結するように UIColor のプライベートメソッドとして定義します

intByCGFloatメソッド、hexStringByCGFloatメソッドは
0.0〜1.0 の CGFloat の値を 0〜255 の値に変換するものと、
その値を 16進数 の文字列に変換するものです

あとは内部で使う定数になります

RGBA構成を取得する

次に以下のようなメソッドを追加します

UIColor+Utility.swift
extension UIColor {

    var floatValues: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        var r:CGFloat = -1, g:CGFloat = -1, b:CGFloat = -1, a:CGFloat = -1
        self.getRed(&r, green: &g, blue: &b, alpha: &a)
        return (r, g, b, a)
    }

    var intValues: (red: Int, green: Int, blue: Int, alpha: Int) {
        let f = self.floatValues
        return (
            UIColor.intByCGFloat(f.red),
            UIColor.intByCGFloat(f.green),
            UIColor.intByCGFloat(f.blue),
            UIColor.intByCGFloat(f.alpha)
        )
    }
}

先ほど定義したのはプライベートなクラスメソッドですが、
これらはパブリックなインスタンスプロパティです

もともと UIColor には

public func getRed(
    red:   UnsafeMutablePointer<CGFloat>,
    green: UnsafeMutablePointer<CGFloat>,
    blue:  UnsafeMutablePointer<CGFloat>,
    alpha: UnsafeMutablePointer<CGFloat>
) -> Bool

というメソッドが用意されていますが
これをそのまま使うのは面倒くさいので
floatValues というプロパティで
赤・緑・青・アルファ値という構成のタプルで返してくれるようにします

let red = UIColor.brownColor().floatValues.red

このように書くと、プリセットされている茶色の赤(R)要素は、0.6であるという風に取得ができます
つまり赤色 60%ということですね

intValues というプロパティでは、255を最大値とした要素の値が取得できます

let red = UIColor.brownColor().intValues.red

上のように書くと 255 の 60% で 153 という数値が取得できます

これらは次以降の処理でも多用するのですが
このメソッド自体が便利なのでパブリックとして定義しておきます

整数とUIColorの相互変換

このあたりから本題になります

UIColor自体は、赤と緑と青、いわゆるRGBの値をそれぞれ渡す形でインスタンスを作ることが多いです
(HSVやグレースケールもありますが)

しかし、デザイナさんとやりとりするためには
HTMLでよく使うカラーコードをもって色の定義をしたほうがメンテナンスがしやすいです

ここでは前項までに定義したメソッドやプロパティを使用して
16進数で定義された整数をもって UIColor を初期化する方法と
逆に UIColor から整数を返すメソッドを定義します

UIColor+Utility.swift
extension UIColor {

    convenience init(rgb: Int) {
        let r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
        let g = CGFloat((rgb & 0x00FF00) >>  8) / 255.0
        let b = CGFloat( rgb & 0x0000FF       ) / 255.0
        self.init(red: r, green: g, blue: b, alpha: 1.0)
    }

    var rgb: Int {
        let i = self.intValues
        return (i.red * 0x010000) + (i.green * 0x000100) + i.blue
    }

    var rgbString: String {
        var r:CGFloat = -1, g:CGFloat = -1, b:CGFloat = -1, a:CGFloat = -1
        self.getRed(&r, green: &g, blue: &b, alpha: &a)

        var ret = ""
        ret += UIColor.hexStringByCGFloat(r)
        ret += UIColor.hexStringByCGFloat(g)
        ret += UIColor.hexStringByCGFloat(b)
        return ret
    }
}

コンビニエンスコンストラクタである init(rgb:) ですが
ここでいう Int型の値は 1 とか 123 とかいう値ではなく
0x6789AB というような16進数ベースの値を渡すことを期待しています

例を書きます

let white = UIColor(rgb: 0xFFFFFF)

これで白色の UIColor が戻ります
人の目に優しくなりました

例えば色を定数で定義しておきたい時に

// before
let headerColor = UIColor(red: 0.274, green: 0.811, blue: 0.239, alpha: 1.0)

// after
let headerColor = UIColor(rgb: 0x46CF3D)

どちらのほうがデザイナさんに優しいかは一目瞭然です

rgbプロパティと、rgbStringプロパティは
逆にUIColorを整数、またはカラーコード文字列で取得するためのものです

let headerColor = UIColor(rgb: 0x46CF3D)
print(headerColor.rgb) // 4640573を出力
print(headerColor.rgbString) // "46CF3D"を出力

このように取得できます

用途としては、DBなどに保存するのに使ったり、あるいは表示をしたり
といったところでしょうか

アルファ値も含めたRGBA値で扱う

前項は RGB値の整数と UIColor の変換を行いましたが、
透過率であるアルファ値(A値)も UIColor には大事な要素ですね

RGB値だけではなく、RGBA値でも同じように使えるようにしてみます

UIColor+Utility.swift
extension UIColor {

    convenience init(rgba: Int) {
        let r = CGFloat((rgba & 0xFF000000) >> 24) / 255.0
        let g = CGFloat((rgba & 0x00FF0000) >> 16) / 255.0
        let b = CGFloat((rgba & 0x0000FF00) >>  8) / 255.0
        let a = CGFloat( rgba & 0x000000FF       ) / 255.0
        self.init(red: r, green: g, blue: b, alpha: a)
    }

    var rgba: Int {
        let i = self.intValues
        return (i.red * 0x01000000) + (i.green * 0x00010000) + (i.blue *  0x00000100) + i.alpha
    }

    var rgbaString: String {
        var r:CGFloat = -1, g:CGFloat = -1, b:CGFloat = -1, a:CGFloat = -1
        self.getRed(&r, green: &g, blue: &b, alpha: &a)

        var ret = ""
        ret += UIColor.hexStringByCGFloat(r)
        ret += UIColor.hexStringByCGFloat(g)
        ret += UIColor.hexStringByCGFloat(b)
        ret += UIColor.hexStringByCGFloat(a)
        return ret
    }
}

先ほどと使い方は一緒ですが、
渡す整数値はアルファ値も含めなければいけません

let headerColor = UIColor(rgba: 0x46CF3DCC)

// このように渡すと意図した色になりません
let headerColor = UIColor(rgba: 0x46CF3D)

ここで CC というアルファ値を定義していることで、0.8 程度の透過色になります

カラーコード文字列からUIColorへの変換

今まで作ったメソッドでも充分便利ではありますが
最後にもうひとつ

たとえば、WebAPIなどの外部リソース(たとえばJSON)から色の指定が返されるなどする場合は
受け取り側であるアプリとのやりとりは文字列で行われることになると思います
それで使われるのは、HTMLカラーコードがほとんどではないでしょうか

{"name":"headerColor", "color":"#46CF3D"}

前項までは
16進の整数値から UIColor を作る。または逆に整数値を返す機能をつけました
そして UIColor からカラーコードの文字列を返す機能もつけましたが
カラーコード文字列から UIColor を作るという機能は備わっていません

カラーコード文字列で相互変換できるようにしてみます

UIColor+Utility.swift
extension UIColor {

    convenience init(colorCode: String) {
        self.init(rgba: UIColor.colorCodeToHex(colorCode))
    }

    class func colorCodeToHex(colorCode: String) -> Int {
        var colorCode = colorCode
        if colorCode.hasPrefix(self.prefix) {
            colorCode = self.substring(colorCode, location: 1)
        }

        switch colorCode.characters.count {
        case 8: // e.g. 35D24CFF
            break
        case 6: // e.g. 35D24C
            colorCode += "FF"
        case 4: // e.g. 35DF
            let r = self.substring(colorCode, location:0, length: 1)
            let g = self.substring(colorCode, location:1, length: 1)
            let b = self.substring(colorCode, location:2, length: 1)
            let a = self.substring(colorCode, location:3, length: 1)
            colorCode = "\(r)\(r)\(g)\(g)\(b)\(b)\(a)\(a)"
        case 3: // e.g. 35D
            let r = self.substring(colorCode, location:0, length: 1)
            let g = self.substring(colorCode, location:1, length: 1)
            let b = self.substring(colorCode, location:2, length: 1)
            colorCode = "\(r)\(r)\(g)\(g)\(b)\(b)FF"
        default: return self.minHex
        }

        if (colorCode as NSString).rangeOfString("^[a-fA-F0-9]+$", options: .RegularExpressionSearch).location == NSNotFound {
            return self.minHex
        }

        var ret: UInt32 = 0
        NSScanner(string: colorCode).scanHexInt(&ret)
        return Int(ret)
    }

    class func hexToColorCode(hex: Int, withPrefix prefix: Bool = true) -> String {
        var hex = hex
        if hex > self.maxHex {
           hex = self.maxHex
        }

        let r = CGFloat((hex & 0xFF000000) >> 24) / 255.0
        let g = CGFloat((hex & 0x00FF0000) >> 16) / 255.0
        let b = CGFloat((hex & 0x0000FF00) >>  8) / 255.0
        let a = CGFloat( hex & 0x000000FF       ) / 255.0

        var ret = ""
        ret += self.hexStringByCGFloat(r)
        ret += self.hexStringByCGFloat(g)
        ret += self.hexStringByCGFloat(b)
        ret += self.hexStringByCGFloat(a)
        if prefix {
            ret = self.prefix + ret
        }
        return ret
    }
}

ずいぶんと長い実装になりました

colorCodeToHexhexToColorCode の両メソッドは対になっています

hexToColorCodeでやっていることは
RGBA値から UIColor を、そしてUIColor からカラーコードを作るところでやっていたこととほぼ同じです
合わせ技で整数値からカラーコードを直接作る処理になっています

colorCodeToHexメソッドは
WEBのCSSまたはHTMLで使用されるカラーコードの大半を吸収できるようにしています

例えば

  • "#FFFFFF"
  • "#FFFFFFFF"
  • "#FFF"
  • "#FFFF"
  • "FFFFFF"
  • "FFFFFFFF"
  • "FFF"
  • "FFFF"

いずれの形式で渡されても
RGBAの整数値に変換できるようにしています

また、おかしな文字列が渡されるなどしてカラーコードを色として認識できない場合は
0x0 すなわち UIColor.clearColor と同じ値を返すようにしています

これで

let headerColor = UIColor(rgb: 0x46CF3D)
let headerColor = UIColor(colorCode: "#46CF3D")

文字列からも整数からも UIColor を作ることができるようになりました

ソースコード

今回のソースコードをまとめると以下のようになります
コピペでも使えるとは思います

UIColor+Utility.swift
extension UIColor {

    var floatValues: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        var r:CGFloat = -1, g:CGFloat = -1, b:CGFloat = -1, a:CGFloat = -1
        self.getRed(&r, green: &g, blue: &b, alpha: &a)
        return (r, g, b, a)
    }

    var intValues: (red: Int, green: Int, blue: Int, alpha: Int) {
        let f = self.floatValues
        return (
            UIColor.intByCGFloat(f.red),
            UIColor.intByCGFloat(f.green),
            UIColor.intByCGFloat(f.blue),
            UIColor.intByCGFloat(f.alpha)
        )
    }
}

extension UIColor {

    private static let maxHex = 0xFFFFFFFF
    private static let minHex = 0x0

    private static let prefix = "#"

    private class func substring(string: String, location: Int, length: Int? = nil) -> String {
        let strlen = string.characters.count
        var len = length ?? strlen
        if location + len > strlen {
            len = strlen - location
        }
        return (string as NSString).substringWithRange(NSMakeRange(location, len))
    }

    private class func intByCGFloat(v: CGFloat) -> Int {
        return Int(round(v * 255.0))
    }

    private class func hexStringByCGFloat(v: CGFloat) -> String {
        let n = self.intByCGFloat(v)
        return NSString(format: "%02X", n) as String
    }
}

extension UIColor {

    convenience init(rgb: Int) {
        let r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
        let g = CGFloat((rgb & 0x00FF00) >>  8) / 255.0
        let b = CGFloat( rgb & 0x0000FF       ) / 255.0
        self.init(red: r, green: g, blue: b, alpha: 1.0)
    }

    var rgb: Int {
        let i = self.intValues
        return (i.red * 0x010000) + (i.green * 0x000100) + i.blue
    }

    var rgbString: String {
        var r:CGFloat = -1, g:CGFloat = -1, b:CGFloat = -1, a:CGFloat = -1
        self.getRed(&r, green: &g, blue: &b, alpha: &a)

        var ret = ""
        ret += UIColor.hexStringByCGFloat(r)
        ret += UIColor.hexStringByCGFloat(g)
        ret += UIColor.hexStringByCGFloat(b)
        return ret
    }
}

extension UIColor {

    convenience init(rgba: Int) {
        let r = CGFloat((rgba & 0xFF000000) >> 24) / 255.0
        let g = CGFloat((rgba & 0x00FF0000) >> 16) / 255.0
        let b = CGFloat((rgba & 0x0000FF00) >>  8) / 255.0
        let a = CGFloat( rgba & 0x000000FF       ) / 255.0
        self.init(red: r, green: g, blue: b, alpha: a)
    }

    var rgba: Int {
        let i = self.intValues
        return (i.red * 0x01000000) + (i.green * 0x00010000) + (i.blue *  0x00000100) + i.alpha
    }

    var rgbaString: String {
        var r:CGFloat = -1, g:CGFloat = -1, b:CGFloat = -1, a:CGFloat = -1
        self.getRed(&r, green: &g, blue: &b, alpha: &a)

        var ret = ""
        ret += UIColor.hexStringByCGFloat(r)
        ret += UIColor.hexStringByCGFloat(g)
        ret += UIColor.hexStringByCGFloat(b)
        ret += UIColor.hexStringByCGFloat(a)
        return ret
    }
}

extension UIColor {

    convenience init(colorCode: String) {
        self.init(rgba: UIColor.colorCodeToHex(colorCode))
    }

    class func colorCodeToHex(colorCode: String) -> Int {
        var colorCode = colorCode
        if colorCode.hasPrefix(self.prefix) {
            colorCode = self.substring(colorCode, location: 1)
        }

        switch colorCode.characters.count {
        case 8: // e.g. 35D24CFF
            break
        case 6: // e.g. 35D24C
            colorCode += "FF"
        case 4: // e.g. 35DF
            let r = self.substring(colorCode, location:0, length: 1)
            let g = self.substring(colorCode, location:1, length: 1)
            let b = self.substring(colorCode, location:2, length: 1)
            let a = self.substring(colorCode, location:3, length: 1)
            colorCode = "\(r)\(r)\(g)\(g)\(b)\(b)\(a)\(a)"
        case 3: // e.g. 35D
            let r = self.substring(colorCode, location:0, length: 1)
            let g = self.substring(colorCode, location:1, length: 1)
            let b = self.substring(colorCode, location:2, length: 1)
            colorCode = "\(r)\(r)\(g)\(g)\(b)\(b)FF"
        default: return self.minHex
        }

        if (colorCode as NSString).rangeOfString("^[a-fA-F0-9]+$", options: .RegularExpressionSearch).location == NSNotFound {
            return self.minHex
        }

        var ret: UInt32 = 0
        NSScanner(string: colorCode).scanHexInt(&ret)
        return Int(ret)
    }

    class func hexToColorCode(hex: Int, withPrefix prefix: Bool = true) -> String {
        var hex = hex
        if hex > self.maxHex {
           hex = self.maxHex
        }

        let r = CGFloat((hex & 0xFF000000) >> 24) / 255.0
        let g = CGFloat((hex & 0x00FF0000) >> 16) / 255.0
        let b = CGFloat((hex & 0x0000FF00) >>  8) / 255.0
        let a = CGFloat( hex & 0x000000FF       ) / 255.0

        var ret = ""
        ret += self.hexStringByCGFloat(r)
        ret += self.hexStringByCGFloat(g)
        ret += self.hexStringByCGFloat(b)
        ret += self.hexStringByCGFloat(a)
        if prefix {
            ret = self.prefix + ret
        }
        return ret
    }
}
13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10