【Swift】Tips: あると便利だったextension達(UIColor編)

はじめに

モバイルアプリサービス部の中安です。

アプリを作ってきた際に、あるとなかなか便利だったextensionをボチボチとご紹介していければと思っています。 アプリの目的に合わせてやりかえる必要があったり、もっとよい実装があるかもしれませんが、何かの役に立てば光栄です。

今回はUIColor編です。

整数値からUIColorを作る

UIColorには init(red:green:blue:alpha:) というイニシャライザが用意されています。 ここに渡す値は 0.0〜1.0 の CGFloat値ですが、カラーピッカーなどを作る際や色定義などをする際に 0〜255 の整数のほうが人の目には分かりよい場合もあります。

下記では 0〜255 のRGB値をラベルなしで渡すことで、定義の際に見やすい表示になるようにしました。

extension UIColor {
    
    convenience init(_ red: Int, _ green: Int, _ blue: Int, _ alpha: Int = 255) {
        let rgba = [red, green, blue, alpha].map { i -> CGFloat in
            switch i {
            case let i where i < 0:
                return 0
            case let i where i > 255:
                return 1
            default:
                return CGFloat(i) / 255
            }
        }
        self.init(red: rgba[0], green: rgba[1], blue: rgba[2], alpha: rgba[3])
    }
}

使い方例

// 背景が朱色のような色になる
self.view.backgroundColor = UIColor(255, 100, 0)

16進数からUIColorを作る

色の指定はHTML,CSSでよく使われる 000000〜FFFFFF のカラーコードでやり取りされることが多いです。 デザイナさんからの指定がカラーコードで来ることが多いのであればカラーコードから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)
    }
    
    convenience init(rgba: Int) {
        let r: CGFloat = CGFloat((rgba & 0xFF000000) >> 24) / 255.0
        let g: CGFloat = CGFloat((rgba & 0x00FF0000) >> 16) / 255.0
        let b: CGFloat = CGFloat((rgba & 0x0000FF00) >>  8) / 255.0
        let a: CGFloat = CGFloat( rgba & 0x000000FF       ) / 255.0
        self.init(red: r, green: g, blue: b, alpha: a)
    }
}

使い方例

// 背景が薄い青色のような色になる
self.view.backgroundColor = UIColor(rgb: 0xD9E5FF)

文字列を渡すパターン

ユーザに直接カラーコードを入力させたり、どこかのHTMLファイルをスクレイピングしたりする際は、それを16進数の数値に変えるよりも下記の用にした方がいいかもしれません。

extension UIColor {
    
    convenience init(code: String) {
        var color: UInt32 = 0
        var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0
        if Scanner(string: code.replacingOccurrences(of: "#", with: "")).scanHexInt32(&color) {
            r = CGFloat((color & 0xFF0000) >> 16) / 255.0
            g = CGFloat((color & 0x00FF00) >>  8) / 255.0
            b = CGFloat( color & 0x0000FF       ) / 255.0
        }
        self.init(red: r, green: g, blue: b, alpha: 1.0)
    }
}

使い方例

// 背景が薄い青色のような色になる
self.view.backgroundColor = UIColor(code: "#D9E5FF")

UIColorのカラーコードを取得する

前項の逆パターンで、現在の色がカラーコードであればどう表現されるか…というのを、カラーピッカーなどを作成するときには必要になるかもしれません。計算プロパティでお気軽に取得できるようになると便利です。

extension UIColor {
    
    var rgbString: String {
        var rgb: [CGFloat] = [-1, -1, -1]
        getRed(&rgb[0], green: &rgb[1], blue: &rgb[2], alpha: nil)
        return rgb.reduce("") { res, value in
            let intval = Int(round(value * 255))
            return res + (NSString(format: "%02X", intval) as String)
        }
    }
    
    var rgbaString: String {
        var rgba: [CGFloat] = [-1, -1, -1, 1]
        getRed(&rgba[0], green: &rgba[1], blue: &rgba[2], alpha: &rgba[3])
        return rgba.reduce("") { res, value in
            let intval = Int(round(value * 255))
            return res + (NSString(format: "%02X", intval) as String)
        }
    }
}

使い方例

let color = UIColor(code: "#ABCDEF")
print(color.rgbString) // "ABCDEF"

UIColorのRGB要素値を個別に取得する

色のRGB値は UIColor の getRed(_:green:blue:alpha:) で取得することができますが、「変数宣言して、変数ポインタを渡して」みたいな手続きが発生します。それを簡略化できるプロパティを用意しておくと案外便利でした。

extension UIColor {
    
    var redComponent: CGFloat {
        return rgbaComponent(at: 0)
    }
    
    var blueComponent: CGFloat {
        return rgbaComponent(at: 1)
    }
    
    var greenComponent: CGFloat {
        return rgbaComponent(at: 2)
    }
    
    var alphaComponent: CGFloat {
        return rgbaComponent(at: 3)
    }
    
    private func rgbaComponent(at index: Int) -> CGFloat {
        var rgba: [CGFloat] = [-1, -1, -1, -1]
        getRed(&rgba[0], green: &rgba[1], blue: &rgba[2], alpha: &rgba[3])
        return rgba[index]
    }
}

わざわざ 〜Component と名前を付けてるのは、UIColor.red などの定義と重複するという警告が出てしまうことへの対策になります。

使い方例

let color = UIColor(code: "#ABCDEF")
print(color.redComponent) // 0.670588235294118
print(color.greenComponent) // 0.803921568627451
print(color.blueComponent) // 0.937254901960784
print(color.alphaComponent) // 1.0

UIColorのRGB要素値を個別に調整した新しいインスタンスを作る

UIColor には withAlphaComponent(_:) というアルファ値を調整した新しいUIColorを作ってくれるメソッドが用意されています。ただし、アルファ値しかないので、他のRGB要素もそれができるように拡張しました。

使用用途はなかなか狭いですが、これもまたカラーピッカーなどを作る際には役立ってくれます。

extension UIColor {
    
    func withRedComponent(_ red: CGFloat) -> UIColor {
        return withRGBComponent(at: 0, value: red)
    }
    
    func withGreenComponent(_ green: CGFloat) -> UIColor {
        return withRGBComponent(at: 1, value: green)
    }
    
    func withBlueComponent(_ blue: CGFloat) -> UIColor {
        return withRGBComponent(at: 2, value: blue)
    }
    
    private func withRGBComponent(at index: Int, value: CGFloat) -> UIColor {
        var rgba: [CGFloat] = [-1, -1, -1, -1]
        getRed(&rgba[0], green: &rgba[1], blue: &rgba[2], alpha: &rgba[3])
        rgba[index] = (value > 1) ? 1 : ((value < 0) ? 0 : value)
        return UIColor(red: rgba[0], green: rgba[1], blue: rgba[2], alpha: rgba[3])
    }
}

使い方例

let color = UIColor(code: "#ABCDEF")
print(color.withRedComponent(1).rgbaString) // "FFCDEFFF"
print(color.withGreenComponent(1).rgbaString) // "ABFFEFFF"
print(color.withBlueComponent(1).rgbaString) // "ABCDFFFF"
print(color.withAlphaComponent(0.5).rgbaString) // "ABCDEF80"

ランダムな色を取得する

実行のたびに違う色を返すクラスメソッドです。 アプリ自体で使用する用途はあまり浮かびませんが、レイアウトで領域や制約などを決める際に適当な色が欲しいと思った時によく使う手です。

extension UIColor {

    class var random: UIColor {
        let rgb = (0..<3).map { _ -> CGFloat in
            return CGFloat(arc4random_uniform(255)) / 255
        }
        return UIColor(red: rgb[0], green: rgb[1], blue: rgb[2], alpha: 1.0)
    }
}

使い方例

// 何色が返るかは謎
let color = UIColor.random

カラーリテラル

直接この記事の内容には関係ないですが、色の定義であれば「カラーリテラル」を使用するのも当然アリです。

カラーリテラルとは、Xcode上のエディタに上に色を直接置く方法です。

1

見た目上は何色かが視覚的にズドンと入ってきますが、中身はやはり CGFloat値で定義されているので、ささっと変更するにはちょっと手間かなという印象です。この辺は好みかと思いますが。

let color = #colorLiteral(red: 1, green: 0.439591527, blue: 0.5583261251, alpha: 1)

まとめ

これから何回かに分けてこのシリーズをやっていければと思います。何かのお役に立ちますでしょうか。