LoginSignup
8
8

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-16

経緯

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

環境

・macOS Catalina
・Xcode 11.7
・Swift 5

Assetsに画像を追加

・千鳥柄の画像 × 2枚
※なくても良い。(alphaが1より低いときにわかりやすくするため)
1枚目 -> サイズ:220 × 37.5 、名前:chidori_s
2枚目 -> サイズ:290 × 290 、名前:chidori
・Hueスライダー用の画像
サイズ:300×13、名前:colors
画像は私のブログ記事から入手できます。

全コード

ボタンを押すとColor Pickerが現れます。


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

まとめ

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

もっと詳しく

8
8
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
8
8