简单的快速颜色选择器弹出窗口(iOS)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27208386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 03:51:14  来源:igfitidea点击:

Simple swift color picker popover (iOS)

iosswiftpopovercolor-pickercolor-palette

提问by Ethan Strider

Is there is a simple way to implement a color picker popover in swift? Are there any built-in libraries or UI elements that I could leverage for this purpose? I saw some color pickers written in objective-c, but they were several years old and I was wondering if there was something more recent.

有没有一种简单的方法可以快速实现颜色选择器弹出框?是否有任何内置库或 UI 元素可以用于此目的?我看到一些用objective-c编写的颜色选择器,但它们已经有好几年了,我想知道是否有更新的东西。

回答by Joel Teply

Here's one I made which is as simple as it gets. It's just a lightweight UIView that allows you to specify the element size in case you want blocked regions (elementSize > 1). It draws itself in interface builder so you can set element size and see the consequences. Just set one of your views in interface builder to this class and then set yourself as a delegate. It will tell you when someone either taps or drags on it and the uicolor at that location. It will draw itself to its own bounds and there's no need for anything other than this class, no image required.

这是我制作的一个,它很简单。它只是一个轻量级的 UIView,允许您指定元素大小,以防您需要阻止区域 (elementSize > 1)。它在界面构建器中绘制自己,因此您可以设置元素大小并查看结果。只需将界面构建器中的一个视图设置为此类,然后将自己设置为委托。它会告诉您何时有人点击或拖动它以及该位置的 uicolor。它将自己绘制到自己的边界,除了这个类之外不需要任何东西,不需要图像。

Element size=1 (Default)element size=1

元素大小=1(默认)元素大小=1

Element size=10
element size=10

元素大小=10
元素大小=10

internal protocol HSBColorPickerDelegate : NSObjectProtocol {
    func HSBColorColorPickerTouched(sender:HSBColorPicker, color:UIColor, point:CGPoint, state:UIGestureRecognizerState)
}

@IBDesignable
class HSBColorPicker : UIView {

    weak internal var delegate: HSBColorPickerDelegate?
    let saturationExponentTop:Float = 2.0
    let saturationExponentBottom:Float = 1.3

    @IBInspectable var elementSize: CGFloat = 1.0 {
        didSet {
            setNeedsDisplay()
        }
    }

    private func initialize() {
        self.clipsToBounds = true
        let touchGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.touchedColor(gestureRecognizer:)))
        touchGesture.minimumPressDuration = 0
        touchGesture.allowableMovement = CGFloat.greatestFiniteMagnitude
        self.addGestureRecognizer(touchGesture)
    }

   override init(frame: CGRect) {
        super.init(frame: frame)
        initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initialize()
    }

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        for y : CGFloat in stride(from: 0.0 ,to: rect.height, by: elementSize) {
            var saturation = y < rect.height / 2.0 ? CGFloat(2 * y) / rect.height : 2.0 * CGFloat(rect.height - y) / rect.height
            saturation = CGFloat(powf(Float(saturation), y < rect.height / 2.0 ? saturationExponentTop : saturationExponentBottom))
            let brightness = y < rect.height / 2.0 ? CGFloat(1.0) : 2.0 * CGFloat(rect.height - y) / rect.height
            for x : CGFloat in stride(from: 0.0 ,to: rect.width, by: elementSize) {
                let hue = x / rect.width
                let color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
                context!.setFillColor(color.cgColor)
                context!.fill(CGRect(x:x, y:y, width:elementSize,height:elementSize))
            }
        }
    }

    func getColorAtPoint(point:CGPoint) -> UIColor {
        let roundedPoint = CGPoint(x:elementSize * CGFloat(Int(point.x / elementSize)),
                               y:elementSize * CGFloat(Int(point.y / elementSize)))
        var saturation = roundedPoint.y < self.bounds.height / 2.0 ? CGFloat(2 * roundedPoint.y) / self.bounds.height
        : 2.0 * CGFloat(self.bounds.height - roundedPoint.y) / self.bounds.height
        saturation = CGFloat(powf(Float(saturation), roundedPoint.y < self.bounds.height / 2.0 ? saturationExponentTop : saturationExponentBottom))
        let brightness = roundedPoint.y < self.bounds.height / 2.0 ? CGFloat(1.0) : 2.0 * CGFloat(self.bounds.height - roundedPoint.y) / self.bounds.height
        let hue = roundedPoint.x / self.bounds.width
        return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
    }

    func getPointForColor(color:UIColor) -> CGPoint {
        var hue: CGFloat = 0.0
        var saturation: CGFloat = 0.0
        var brightness: CGFloat = 0.0
        color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil);

        var yPos:CGFloat = 0
        let halfHeight = (self.bounds.height / 2)
        if (brightness >= 0.99) {
            let percentageY = powf(Float(saturation), 1.0 / saturationExponentTop)
            yPos = CGFloat(percentageY) * halfHeight
        } else {
            //use brightness to get Y
            yPos = halfHeight + halfHeight * (1.0 - brightness)
        }
        let xPos = hue * self.bounds.width
        return CGPoint(x: xPos, y: yPos)
    }

    @objc func touchedColor(gestureRecognizer: UILongPressGestureRecognizer) {
        if (gestureRecognizer.state == UIGestureRecognizerState.began) {
            let point = gestureRecognizer.location(in: self)
            let color = getColorAtPoint(point: point)
            self.delegate?.HSBColorColorPickerTouched(sender: self, color: color, point: point, state:gestureRecognizer.state)
        }        
    }
}

回答by Ethan Strider

I went ahead and wrote a simple color picker popover in Swift. Hopefully it will help someone else out.

我继续在 Swift 中编写了一个简单的颜色选择器弹出窗口。希望它会帮助别人。

https://github.com/EthanStrider/ColorPickerExample

https://github.com/EthanStrider/ColorPickerExample

Image Picker Screenshot

图像选择器截图

回答by Christen Ward

Swift 3.0 version of @joel-teply's answer:

@joel-teply答案的Swift 3.0 版本:

internal protocol HSBColorPickerDelegate : NSObjectProtocol {
    func HSBColorColorPickerTouched(sender:HSBColorPicker, color:UIColor, point:CGPoint, state:UIGestureRecognizerState)
}

@IBDesignable
class HSBColorPicker : UIView {

    weak internal var delegate: HSBColorPickerDelegate?
    let saturationExponentTop:Float = 2.0
    let saturationExponentBottom:Float = 1.3

    @IBInspectable var elementSize: CGFloat = 1.0 {
        didSet {
            setNeedsDisplay()
        }
    }


    private func initialize() {

        self.clipsToBounds = true
        let touchGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.touchedColor(gestureRecognizer:)))
        touchGesture.minimumPressDuration = 0
        touchGesture.allowableMovement = CGFloat.greatestFiniteMagnitude
        self.addGestureRecognizer(touchGesture)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initialize()
    }

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()

        for y in stride(from: (0 as CGFloat), to: rect.height, by: elementSize) {

            var saturation = y < rect.height / 2.0 ? CGFloat(2 * y) / rect.height : 2.0 * CGFloat(rect.height - y) / rect.height
            saturation = CGFloat(powf(Float(saturation), y < rect.height / 2.0 ? saturationExponentTop : saturationExponentBottom))
            let brightness = y < rect.height / 2.0 ? CGFloat(1.0) : 2.0 * CGFloat(rect.height - y) / rect.height

            for x in stride(from: (0 as CGFloat), to: rect.width, by: elementSize) {
                let hue = x / rect.width
                let color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
                context!.setFillColor(color.cgColor)
                context!.fill(CGRect(x:x, y:y, width:elementSize,height:elementSize))
            }
        }
    }

    func getColorAtPoint(point:CGPoint) -> UIColor {
        let roundedPoint = CGPoint(x:elementSize * CGFloat(Int(point.x / elementSize)),
                                   y:elementSize * CGFloat(Int(point.y / elementSize)))
        var saturation = roundedPoint.y < self.bounds.height / 2.0 ? CGFloat(2 * roundedPoint.y) / self.bounds.height
            : 2.0 * CGFloat(self.bounds.height - roundedPoint.y) / self.bounds.height
        saturation = CGFloat(powf(Float(saturation), roundedPoint.y < self.bounds.height / 2.0 ? saturationExponentTop : saturationExponentBottom))
        let brightness = roundedPoint.y < self.bounds.height / 2.0 ? CGFloat(1.0) : 2.0 * CGFloat(self.bounds.height - roundedPoint.y) / self.bounds.height
        let hue = roundedPoint.x / self.bounds.width
        return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
    }

    func getPointForColor(color:UIColor) -> CGPoint {
        var hue:CGFloat=0;
        var saturation:CGFloat=0;
        var brightness:CGFloat=0;
        color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil);

        var yPos:CGFloat = 0
        let halfHeight = (self.bounds.height / 2)

        if (brightness >= 0.99) {
            let percentageY = powf(Float(saturation), 1.0 / saturationExponentTop)
            yPos = CGFloat(percentageY) * halfHeight
        } else {
            //use brightness to get Y
            yPos = halfHeight + halfHeight * (1.0 - brightness)
        }

        let xPos = hue * self.bounds.width

        return CGPoint(x: xPos, y: yPos)
    }

    func touchedColor(gestureRecognizer: UILongPressGestureRecognizer){
        let point = gestureRecognizer.location(in: self)
        let color = getColorAtPoint(point: point)

        self.delegate?.HSBColorColorPickerTouched(sender: self, color: color, point: point, state:gestureRecognizer.state)
    }
}

回答by Michael Ros

Based on Joel Teply code (Swift 4), with gray bar on top:

基于 Joel Teply 代码 (Swift 4),顶部带有灰色条:

import UIKit


class ColorPickerView : UIView {

var onColorDidChange: ((_ color: UIColor) -> ())?

let saturationExponentTop:Float = 2.0
let saturationExponentBottom:Float = 1.3

let grayPaletteHeightFactor: CGFloat = 0.1
var rect_grayPalette = CGRect.zero
var rect_mainPalette = CGRect.zero

// adjustable
var elementSize: CGFloat = 1.0 {
    didSet {
        setNeedsDisplay()
    }
}

override init(frame: CGRect) {
    super.init(frame: frame)
    setup()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setup()
}

private func setup() {

    self.clipsToBounds = true
    let touchGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.touchedColor(gestureRecognizer:)))
    touchGesture.minimumPressDuration = 0
    touchGesture.allowableMovement = CGFloat.greatestFiniteMagnitude
    self.addGestureRecognizer(touchGesture)
}



override func draw(_ rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()

    rect_grayPalette = CGRect(x: 0, y: 0, width: rect.width, height: rect.height * grayPaletteHeightFactor)
    rect_mainPalette = CGRect(x: 0, y: rect_grayPalette.maxY,
                              width: rect.width, height: rect.height - rect_grayPalette.height)

    // gray palette
    for y in stride(from: CGFloat(0), to: rect_grayPalette.height, by: elementSize) {

        for x in stride(from: (0 as CGFloat), to: rect_grayPalette.width, by: elementSize) {
            let hue = x / rect_grayPalette.width

            let color = UIColor(white: hue, alpha: 1.0)

            context!.setFillColor(color.cgColor)
            context!.fill(CGRect(x:x, y:y, width:elementSize, height:elementSize))
        }
    }

    // main palette
    for y in stride(from: CGFloat(0), to: rect_mainPalette.height, by: elementSize) {

        var saturation = y < rect_mainPalette.height / 2.0 ? CGFloat(2 * y) / rect_mainPalette.height : 2.0 * CGFloat(rect_mainPalette.height - y) / rect_mainPalette.height
        saturation = CGFloat(powf(Float(saturation), y < rect_mainPalette.height / 2.0 ? saturationExponentTop : saturationExponentBottom))
        let brightness = y < rect_mainPalette.height / 2.0 ? CGFloat(1.0) : 2.0 * CGFloat(rect_mainPalette.height - y) / rect_mainPalette.height

        for x in stride(from: (0 as CGFloat), to: rect_mainPalette.width, by: elementSize) {
            let hue = x / rect_mainPalette.width

            let color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)

            context!.setFillColor(color.cgColor)
            context!.fill(CGRect(x:x, y: y + rect_mainPalette.origin.y,
                                 width: elementSize, height: elementSize))
        }
    }
}



func getColorAtPoint(point: CGPoint) -> UIColor
{
    var roundedPoint = CGPoint(x:elementSize * CGFloat(Int(point.x / elementSize)),
                               y:elementSize * CGFloat(Int(point.y / elementSize)))

    let hue = roundedPoint.x / self.bounds.width


    // main palette
    if rect_mainPalette.contains(point)
    {
        // offset point, because rect_mainPalette.origin.y is not 0
        roundedPoint.y -= rect_mainPalette.origin.y

        var saturation = roundedPoint.y < rect_mainPalette.height / 2.0 ? CGFloat(2 * roundedPoint.y) / rect_mainPalette.height
            : 2.0 * CGFloat(rect_mainPalette.height - roundedPoint.y) / rect_mainPalette.height

        saturation = CGFloat(powf(Float(saturation), roundedPoint.y < rect_mainPalette.height / 2.0 ? saturationExponentTop : saturationExponentBottom))
        let brightness = roundedPoint.y < rect_mainPalette.height / 2.0 ? CGFloat(1.0) : 2.0 * CGFloat(rect_mainPalette.height - roundedPoint.y) / rect_mainPalette.height

        return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
    }
    // gray palette
    else{

        return UIColor(white: hue, alpha: 1.0)
    }
}


@objc func touchedColor(gestureRecognizer: UILongPressGestureRecognizer){
    let point = gestureRecognizer.location(in: self)
    let color = getColorAtPoint(point: point)

    self.onColorDidChange?(color)
  }
}

Usage:

用法:

    let colorPickerView = ColorPickerView()
    colorPickerView.onColorDidChange = { [weak self] color in
        DispatchQueue.main.async {

            // use picked color for your needs here...
            self?.view.backgroundColor = color
        }

    }

    // add it to some view and set constraints
    ...

回答by Christian1313

Thanks for the starting point.

感谢您的起点。

I took it from there and wrote a complte Color PickerViewController with a custom UIView and some drawing code.

我从那里拿了它并用自定义 UIView 和一些绘图代码编写了一个完整的 Color PickerViewController。

I made the custom UIView @IBDesignable so it can be rendered in InterfaceBuilder.

我制作了自定义 UIView @IBDesignable 以便它可以在 InterfaceBuilder 中呈现。

https://github.com/Christian1313/iOS_Swift_ColorPicker

https://github.com/Christian1313/iOS_Swift_ColorPicker

回答by extrum

ColorPickerViewImage

颜色选择器视图图像

Based on Christian1313 answer, I added darker colors:

根据 Christian1313 的回答,我添加了较深的颜色

@IBDesignable final public class SwiftColorView: UIView {

weak var colorSelectedDelegate: ColorDelegate?

@IBInspectable public var numColorsX:Int =  10 {
    didSet {
        setNeedsDisplay()
    }
}

@IBInspectable public var numColorsY:Int = 18 {
    didSet {
        setNeedsDisplay()
    }
}

@IBInspectable public var coloredBorderWidth:Int = 10 {
    didSet {
        setNeedsDisplay()
    }
}

@IBInspectable public var showGridLines:Bool = false {
    didSet {
        setNeedsDisplay()
    }
}

weak var delegate: SwiftColorPickerDataSource?

public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }
    let location = touch.location(in: self)

    colorSelectedDelegate?.setStroke(color: colorAtPoint(point: location))
}

public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }
    let location = touch.location(in: self)

    colorSelectedDelegate?.setStroke(color: colorAtPoint(point: location))
}

public override func draw(_ rect: CGRect) {
    super.draw(rect)
    let lineColor = UIColor.gray
    let pS = patternSize()
    let w = pS.w
    let h = pS.h

    for y in 0..<numColorsY
    {
        for x in 0..<numColorsX
        {
            let path = UIBezierPath()
            let start = CGPoint(x: CGFloat(x)*w+CGFloat(coloredBorderWidth), y: CGFloat(y)*h+CGFloat(coloredBorderWidth))
            path.move(to: start);
            path.addLine(to: CGPoint(x: start.x+w, y: start.y))
            path.addLine(to: CGPoint(x: start.x+w, y: start.y+h))
            path.addLine(to: CGPoint(x: start.x, y: start.y+h))
            path.addLine(to: start)
            path.lineWidth = 0.25
            colorForRectAt(x: x,y:y).setFill();

            if (showGridLines)
            {
                lineColor.setStroke()
            }
            else
            {
                colorForRectAt(x: x, y: y).setStroke();
            }
            path.fill();
            path.stroke();
        }
    }
}

private func colorForRectAt(x: Int, y: Int) -> UIColor
{

    if let ds = delegate {
        return ds.colorForPalletIndex(x: x, y: y, numXStripes: numColorsX, numYStripes: numColorsY)
    } else {

        var hue:CGFloat = CGFloat(x) / CGFloat(numColorsX)
        var fillColor = UIColor.white
        if (y==0)
        {
            if (x==(numColorsX-1))
            {
                hue = 1.0;
            }
            fillColor = UIColor(white: hue, alpha: 1.0);
        }
        else
        {
            if y < numColorsY / 2 {
                //dark
                let length = numColorsY / 2
                let brightness: CGFloat = CGFloat(y) / CGFloat(length)
                fillColor = UIColor(hue: hue, saturation: 1.0, brightness: brightness, alpha: 1.0)
            } else if y == numColorsY / 2 {
                // normal
                fillColor = UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0)
            } else {
                // light
                let length = numColorsY / 2 - 1
                let offset = y - length - 1
                let sat:CGFloat = CGFloat(1.0) - CGFloat(offset) / CGFloat(length + 1)
                print("sat", sat)
                fillColor = UIColor(hue: hue, saturation: sat, brightness: 1.0, alpha: 1.0)
            }
        }
        return fillColor
    }
}

func colorAtPoint(point: CGPoint) -> UIColor
{
    let pS = patternSize()
    let w = pS.w
    let h = pS.h
    let x = (point.x-CGFloat(coloredBorderWidth))/w
    let y = (point.y-CGFloat(coloredBorderWidth))/h
    return colorForRectAt(x: Int(x), y:Int(y))
}

private func patternSize() -> (w: CGFloat, h:CGFloat)
{
    let width = self.bounds.width-CGFloat(2*coloredBorderWidth)
    let height = self.bounds.height-CGFloat(2*coloredBorderWidth)

    let w = width/CGFloat(numColorsX)
    let h = height/CGFloat(numColorsY)
    return (w,h)
}

public override func prepareForInterfaceBuilder()
{
    print("Compiled and run for IB")
}

}

}