Simple swift color picker popover (iOS)

前端 未结 8 1162
被撕碎了的回忆
被撕碎了的回忆 2020-12-12 13:30

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 c

相关标签:
8条回答
  • 2020-12-12 13:31

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

    https://github.com/EthanStrider/ColorPickerExample

    Image Picker Screenshot

    0 讨论(0)
  • With iOS 14 Apple has now implemented a standard UIColorPickerViewController and associated UIColorWell that is a color swatch that automatically brings up the UIColorPicker to choose a color.

    You can test the ColorPicker by creating a Swift App project with Xcode 12 or later, targeting iOS 14+ and then try this simple code:

    import SwiftUI
    
    struct ContentView: View {
        @State private var bgColor = Color.white
    
        var body: some View {
            VStack {
                ColorPicker("Set the background color",
                            selection: $bgColor,
                            supportsOpacity: true)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(bgColor)
        }
    }
    

    Change supportsOpacity to false to get rid of the opacity slider and only allow fully opaque colors.

    ColorPicker showing two different modes of selection:

    ColorPicker without alpha:

    0 讨论(0)
  • 2020-12-12 13:34

    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.

    Element size=1 (Default)

    Element size=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)
            }        
        }
    }
    
    0 讨论(0)
  • 2020-12-12 13:36

    Thanks for the starting point.

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

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

    https://github.com/Christian1313/iOS_Swift_ColorPicker

    0 讨论(0)
  • 2020-12-12 13:46

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

    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
        ...
    
    0 讨论(0)
  • 2020-12-12 13:51

    using Michael Ros answer,

    If you want to use this view by objective-c viewcontroller you can simply create a new swift file called ColorPickerView and add a uiview to your viewcontroller on the storyboard and select ColorPickerView as it's class name. then make your view controller a notification observer of name @"colorIsPicked"

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateColor) name:@"colorIsPicked" object:nil];
    

    Code for ColorPickerView.swift

    class ColorPickerView : UIView {
    
    @objc public lazy var onColorDidChange: ((_ color: UIColor) -> ()) = {
               
        //send a notification for the caller view to update its elements if necessery
        NotificationCenter.default.post(name: Notification.Name("colorIsPicked"), object: nil)
    
    }
    
    
    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 = 10.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)
    }
     }
    
    0 讨论(0)
提交回复
热议问题