ライブラリを使わずに美しいカラーピッカーを実装【iOSアプリ】【Swift】

#経緯
お絵かきアプリにカラーピッカーを付けたかったのでいろいろ探したのですが、ライブラリを使ったのしか出てこなかった。
...
...
じゃあ自力で１から作っちゃおう！

#環境
・macOS Catalina
・Xcode 11.7
・Swift 5

#Assetsに画像を追加

・千鳥柄の画像 × 2枚
※なくても良い。（alphaが１より低いときにわかりやすくするため）
1枚目 -> サイズ：220 × 37.5 、名前：chidori_s
2枚目 -> サイズ：290 × 290 、名前：chidori
・Hueスライダー用の画像
サイズ：300×13、名前：colors
画像は私の[ブログ記事](http://churikeeno.blog.jp/archives/6827033.html)から入手できます。
#全コード
ボタンを押すとColor Pickerが現れます。

```swift

import UIKit

class ViewController: UIViewController {

    // 現在表示されているSlider
    var sliderNow = ""
    // Hue一時保存用
    var hue: CGFloat!
    // Color Picker を表示するボタン
    var colorBtnNow: UIButton!
    
    // Color Picker
    var picker: UIView!
    var sliderPallete: UIView!
    // Color Pickerの上の白丸
    let thumb = UIView()
    // 変更前、現在の色プレビュー
    var twoColors = [UIButton(), UIButton()]
    // パレットのグラデーションレイヤー
    var gradLayer = [CAGradientLayer(), CAGradientLayer()]
    // Slider
    var colorSlider = [UISlider(), UISlider(), UISlider(), UISlider()]
    // Slider の色
    let sliderGrad = [CAGradientLayer(), CAGradientLayer(), CAGradientLayer(), CAGradientLayer()]
    // 現在のSliderの値
    var numText = [UILabel(), UILabel(), UILabel(), UILabel()]
    
    // 現在の色
    var colorNow: UIColor!
    // 現在のrgba/hsba
    var changingColor = [CGFloat(), CGFloat(), CGFloat(), CGFloat()]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let b = UIButton()
        b.frame = CGRect(x: 10, y: 60, width: 80, height: 50)
        b.backgroundColor = .systemOrange
        b.setTitle("button", for: .normal)
        view.addSubview(b)
        b.addTarget(self, action: #selector(showColorPicker(sender:)), for: .touchUpInside)
    }

    @objc func showColorPicker(sender: UIButton) {
        
        picker = UIView()
        picker.backgroundColor = UIColor(white: 0.2, alpha: 1)
        picker.layer.cornerRadius = 10
        picker.frame = CGRect(x: 60, y: 120, width: 310, height: 615)
        view.addSubview(picker)
        
        colorBtnNow = sender

        let parameters = ["RGB", "HLS"]
        let seg = UISegmentedControl(items: parameters)
        seg.frame = CGRect(x: 95, y: 20, width: 120, height: 35)
        seg.backgroundColor = .gray
        seg.selectedSegmentIndex = 0
        seg.addTarget(self, action: #selector(segmentChanged), for: .valueChanged)
        picker.addSubview(seg)
            
        // 色プレビュー
        let colorView = UIImageView()
        colorView.image = UIImage(named: "chidori_s")
        colorView.frame = CGRect(x: 45, y: 70, width: 220, height: 37.5)
        colorView.clipsToBounds = true
        colorView.layer.cornerRadius = 6
        colorView.isUserInteractionEnabled = true
        picker.addSubview(colorView)
        
        var x: CGFloat = 0
        for c in twoColors {
            c.frame = CGRect(x: x, y: 0, width: 110, height: colorView.frame.height)
            c.backgroundColor = sender.backgroundColor!
            colorView.addSubview(c)
            c.addTarget(self, action: #selector(setColor), for: .touchDown)
            x = 110
        }
        // グラデーションパレット
        let hsb = sender.backgroundColor!.hsb()
        let gradView = UIButton(frame: CGRect(x: 10, y: 120, width: 290, height: 290))
        gradView.addTarget(self, action: #selector(colorGradLayer_tapped), for: .allTouchEvents)
        gradView.setBackgroundImage(UIImage(named: "chidori"), for: .normal)
        
        hue = hsb[0]
        func set_gradLayer(_ g: CAGradientLayer, colors: [CGColor], start: CGPoint) {
            g.colors = colors
            g.startPoint = start
            g.endPoint = CGPoint(x: 0.0, y: 0.0)
            g.frame = CGRect(x: 0, y: 0, width: 290, height: 290)
            g.locations = [0, 1]
            gradView.layer.addSublayer(g)
        }
        set_gradLayer(gradLayer[0],
                      colors: [hsb_color([hsb[0], 1, 1, hsb[3]]).cgColor,
                               UIColor(white: 1, alpha: hsb[3]).cgColor],
                      start: CGPoint(x: 1.0, y: 0.0))
        set_gradLayer(gradLayer[1],
                      colors: [UIColor.black.cgColor, UIColor.clear.cgColor],
                      start: CGPoint(x: 0.0, y: 1.0))
        
        thumb.center = CGPoint(x: 290*hsb[1], y: 290*(1-hsb[2]))
        thumb.frame.size = CGSize(width: 24, height: 24)
        thumb.layer.cornerRadius = 12
        thumb.backgroundColor = .white
        thumb.isUserInteractionEnabled = false
        gradView.addSubview(thumb)
        picker.addSubview(gradView)
        
        setUpRGBView()
    }
    
    @objc func colorGradLayer_tapped(sender: UIButton, event: UIEvent) {
        if let touch = event.touches(for: sender)?.first {
            // タップした場所を取得
            let loc = touch.location(in: sender)
            
            if 0..<290 ~= loc.x { thumb.center.x = loc.x }
            if 0..<290 ~= loc.y { thumb.center.y = loc.y }
            
            changingColor[0] = hue
            changingColor[1] = thumb.center.x/290
            changingColor[2] = 1 - thumb.center.y/290
            
            if sliderNow == "hls" {
                for i in 1...2 { updateSlider(tag: i, value: changingColor[i]) }
            } else {
                changingColor = hsb_color(changingColor).rgb()
                for i in 0...2 { updateSlider(tag: i, value: changingColor[i]) }
            }
            changeColor()
        }
    }
    
    @objc func segmentChanged(_ segment: UISegmentedControl) {
        switch segment.selectedSegmentIndex {
        case 0: setUpRGBView()
        case 1: setUpHLSView()
        default: break
        }
    }
    
    @objc func setColor(sender: UIButton) {
        if sliderNow == "hls" { changingColor = sender.backgroundColor!.hsb()
        } else { changingColor = sender.backgroundColor!.rgb() }
        changeColor()
    }
    
    @objc func setUpHLSView() {
        sliderNow = "hls"
        setSlider(colors: colorBtnNow.backgroundColor!.hsb(), text: ["colors", "satu", "br", "alpha"])
        colorSlider[0].setMinimumTrackImage(UIImage(named: "colors"), for: .normal)
        colorSlider[0].setMaximumTrackImage(UIImage(named: "colors"), for: .normal)
        for s in colorSlider[0].layer.sublayers! { s.removeFromSuperlayer() }
        set_grad_hls(1...3)
    }
    func set_grad_hls(_ range: ClosedRange<Int>) {
        for i in range {
            var c = changingColor
            if i != 3 { c[3] = 1 }
            c[i] = 0
            let color1 = UIColor(hue: c[0], saturation: c[1], brightness: c[2], alpha: c[3])
            c[i] = 1
            let color2 = UIColor(hue: c[0], saturation: c[1], brightness: c[2], alpha: c[3])
            sliderGrad[i].colors = [color1.cgColor, color2.cgColor]
        }
    }
    
    @objc func setUpRGBView() {
        sliderNow = "rgb"
        let color = colorBtnNow.backgroundColor!
        setSlider(colors: color.rgb(), text: ["red", "green", "blue", "alpha"])
        set_grad_rbg(0...3)
    }
    func set_grad_rbg(_ range: ClosedRange<Int>) {
        for i in range {
            var c = changingColor
            if i != 3 { c[3] = 1 }
            c[i] = 0
            let color1 = UIColor(red: c[0], green: c[1], blue: c[2], alpha: c[3])
            c[i] = 1
            let color2 = UIColor(red: c[0], green: c[1], blue: c[2], alpha: c[3])
            sliderGrad[i].colors = [color1.cgColor, color2.cgColor]
        }
    }
    
    func setSlider(colors: [CGFloat], text: [String]) {
        sliderPallete?.removeFromSuperview()
        sliderPallete = UIView(frame: CGRect(x: 0, y: 410, width: 310, height: 250))
        picker.addSubview(sliderPallete)
        for i in 0..<colors.count {
            colorSlider[i] = slider(tag: i, img: text[i], value: colors[i], superV: sliderPallete)
            colorSlider[i].addTarget(self, action: #selector(slider_value_changed), for: .valueChanged)
            changingColor[i] = colors[i]
        }
        for i in 0..<sliderGrad.count {
            sliderGrad[i].frame = CGRect(x: 0, y: 0, width: 220, height: 20)
            sliderGrad[i].startPoint = CGPoint(x: 0.0, y: 0.0)
            sliderGrad[i].endPoint = CGPoint(x: 1.0, y: 0.0)
            sliderGrad[i].locations = [0, 1]
            sliderGrad[i].cornerRadius = 10
            colorSlider[i].layer.addSublayer(sliderGrad[i])
        }
    }
    func slider(tag: Int, img: String, value: CGFloat, superV: UIView) -> UISlider {
        numText[tag].backgroundColor = UIColor.clear
        numText[tag].font = .systemFont(ofSize: 16, weight: UIFont.Weight(6))
        numText[tag].textColor = .white
        superV.addSubview(numText[tag])

        let s = UISlider()
        s.tag = tag
        s.tintColor = .clear
        s.frame = CGRect(x: 65, y: 25+tag*40, width: 220, height: 20)
        superV.addSubview(s)
        if img == "alpha" {
            numText[tag].text = String(format: "%.1f", value)
        } else { numText[tag].text = "\(Int(value*255))"}
        s.maximumValue = 0
        s.maximumValue = 1
        s.setValue(Float(value), animated: false)
        numText[tag].frame = CGRect(x: 14, y: s.frame.origin.y-9, width: 60, height: 35)
        return s
    }
    
    @objc func slider_value_changed(slider: UISlider) {
        updateSlider(tag: slider.tag, value: CGFloat(slider.value))
        changeColor()
        changingColor[slider.tag] = CGFloat(slider.value)
        let c = changingColor
        // update grad layer
        if sliderNow == "hls" {
            hue = c[0]
            switch slider.tag {
            case 0, 3:
                gradLayer[0].colors = [hsb_color(c).cgColor, UIColor(white: 1, alpha: c[3]).cgColor]
                set_grad_hls(1...3)
            case 1: thumb.center.x = 290*c[1]; break
            case 2: thumb.center.y = 290*(1-c[2]); break
            default: break
            }
        } else {
            let hsb = rgb_color(c).hsb()
            hue = hsb[0]
            thumb.center = CGPoint(x: 290*hsb[1], y: 290*(1-hsb[2]))
            gradLayer[0].colors = [hsb_color([hsb[0], 1, 1, hsb[3]]).cgColor,
                                   UIColor(white: 1, alpha: hsb[3]).cgColor]
        }
    }
    
    func updateSlider(tag: Int, value: CGFloat) {
        
        colorSlider[tag].setValue(Float(value), animated: false)
        if tag == 3 { numText[tag].text = String(format: "%.1f", value) }
        else { numText[tag].text = "\(Int(value*255))" }
    }
    
    func changeColor() {
        var color = rgb_color(changingColor)
        if sliderNow == "hls" { color = hsb_color(changingColor) }
        else { set_grad_rbg(0...3) }
        colorBtnNow.backgroundColor = color
        colorNow = color
        twoColors[1].backgroundColor = color
    }
    
    
    func hsb_color(_ c: [CGFloat]) -> UIColor {
        return UIColor(hue: c[0], saturation: c[1], brightness: c[2], alpha: c[3])
    }
    func rgb_color(_ c: [CGFloat]) -> UIColor {
        return UIColor(red: c[0], green: c[1], blue: c[2], alpha: c[3])
    }
}

extension UIColor {
    func hsb() -> [CGFloat] {
        var (h, s, b, a) = (CGFloat(), CGFloat(), CGFloat(), CGFloat())
        getHue(&h, saturation: &s, brightness: &b, alpha: &a)
        return [h, s, b, a]
    }
    func rgb() -> [CGFloat] {
        var (r, g, b, a) = (CGFloat(), CGFloat(), CGFloat(), CGFloat())
        getRed(&r, green: &g, blue: &b, alpha: &a)
        return [r, g, b, a]
    }
}


```

#実装例
![スクリーンショット 0002-09-16 午前11.41.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701442/08f84c3a-b239-0bae-0eff-38984874e9a8.png)

#まとめ
スライダーの色を動的に変えることで
直感的に操作できるUIになりましたね！

[もっと詳しく](http://churikeeno.blog.jp/archives/6827033.html)