ごめんなさい
ここでRGBAの値をIntで扱っていますが、
RGBAの値はIntだとオーバーフローしてしまうことに気づいてしまいました
UInt32に置き換えれば動くのですが
キャスト処理などをこの記事のソースコード全体に行わなければなりません
またどこかで修正します・・・
(2016.07.1 加筆)
まえがき
役に立つかどうかよくわからないTipsシリーズ
今回少し長めです
色を扱うクラスは UIColor
です
これはこれで標準でも便利なクラスですが
もっとお手軽に使えるように色々と拡張してみます
下ごしらえ
UIColor の便利な拡張のために
まずはそれ用のファイルを作ります
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構成を取得する
次に以下のようなメソッドを追加します
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 から整数を返すメソッドを定義します
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値でも同じように使えるようにしてみます
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 を作るという機能は備わっていません
カラーコード文字列で相互変換できるようにしてみます
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
}
}
ずいぶんと長い実装になりました
colorCodeToHex
と hexToColorCode
の両メソッドは対になっています
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 を作ることができるようになりました
ソースコード
今回のソースコードをまとめると以下のようになります
コピペでも使えるとは思います
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
}
}