4

Hi i am trying to write colour picker in swift that looks like this.

enter image description here

But so far I managed this.

enter image description here

Draw circle was easy, heres code...

 fileprivate func setupScene(){

    let circlePath: UIBezierPath = UIBezierPath(arcCenter: CGPoint(x: self.wheelView.frame.width/2, y: self.wheelView.frame.height/2), radius: CGFloat(self.wheelView.frame.height/2), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)

    let shapeLayer = CAShapeLayer()
    shapeLayer.path = circlePath.cgPath

    //color inside circle
    shapeLayer.fillColor = UIColor.clear.cgColor
    //colored border of circle
    shapeLayer.strokeColor = UIColor.purple.cgColor
    //width size of border
    shapeLayer.lineWidth = 10

    wheelView.layer.addSublayer(shapeLayer)
}

@IBOutlet var wheelView: UIView!

But now I don't know how to insert rainbow colours ... I tried CAGradientLayer but it was not visible. Any good advice?

Pan Mluvčí
  • 1,242
  • 2
  • 21
  • 42
  • Circular gradients aren't natively supported. There's some good alternatives here: https://stackoverflow.com/questions/6905466/cgcontextdrawanglegradient – jrturton May 30 '17 at 12:39
  • you wanna refer to this instead and figure out: https://stackoverflow.com/questions/11783114/draw-outer-half-circle-with-gradient-using-core-graphics-in-ios – Dominik Bucher May 30 '17 at 12:57
  • Thank you for response.... I marked answer of Dave C as answered....this is exactly what I was looking for. – Pan Mluvčí May 30 '17 at 13:04
  • Note: for a "proper" pixelwise (or at least point-wise) solution, just go here: https://stackoverflow.com/q/47610272/294884 Indeed, there's a link there to a (very old, but working) drop-in class on github. – Fattie Dec 10 '17 at 18:05
  • **Important! .conic is finally available in 12 it's now one line of code.** – Fattie Jan 01 '20 at 23:23

1 Answers1

15

Details

  • Xcode 9.1, swift 4
  • Xcode 10.2.1 (10E1001), Swift 5

Solution

The code was taken from https://github.com/joncardasis/ChromaColorPicker

import UIKit

class RainbowCircle: UIView {

    private var radius: CGFloat {
        return frame.width>frame.height ? frame.height/2 : frame.width/2
    }

    private var stroke: CGFloat = 10
    private var padding: CGFloat = 5

    //MARK: - Drawing
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        drawRainbowCircle(outerRadius: radius - padding, innerRadius: radius - stroke - padding, resolution: 1)
    }

    init(frame: CGRect, lineHeight: CGFloat) {
        super.init(frame: frame)
        stroke = lineHeight
    }

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

    /*
     Resolution should be between 0.1 and 1
     */
    private func drawRainbowCircle(outerRadius: CGFloat, innerRadius: CGFloat, resolution: Float) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        context.saveGState()
        context.translateBy(x: self.bounds.midX, y: self.bounds.midY) //Move context to center

        let subdivisions:CGFloat = CGFloat(resolution * 512) //Max subdivisions of 512

        let innerHeight = (CGFloat.pi*innerRadius)/subdivisions //height of the inner wall for each segment
        let outterHeight = (CGFloat.pi*outerRadius)/subdivisions

        let segment = UIBezierPath()
        segment.move(to: CGPoint(x: innerRadius, y: -innerHeight/2))
        segment.addLine(to: CGPoint(x: innerRadius, y: innerHeight/2))
        segment.addLine(to: CGPoint(x: outerRadius, y: outterHeight/2))
        segment.addLine(to: CGPoint(x: outerRadius, y: -outterHeight/2))
        segment.close()

        //Draw each segment and rotate around the center
        for i in 0 ..< Int(ceil(subdivisions)) {
            UIColor(hue: CGFloat(i)/subdivisions, saturation: 1, brightness: 1, alpha: 1).set()
            segment.fill()
            //let lineTailSpace = CGFloat.pi*2*outerRadius/subdivisions  //The amount of space between the tails of each segment
            let lineTailSpace = CGFloat.pi*2*outerRadius/subdivisions
            segment.lineWidth = lineTailSpace //allows for seemless scaling
            segment.stroke()

            //Rotate to correct location
            let rotate = CGAffineTransform(rotationAngle: -(CGFloat.pi*2/subdivisions)) //rotates each segment
            segment.apply(rotate)
        }

        context.translateBy(x: -self.bounds.midX, y: -self.bounds.midY) //Move context back to original position
        context.restoreGState()
    }
}

Usage

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let rainbowCircle = RainbowCircle(frame: CGRect(x: 50, y: 50, width: 240, height: 420), lineHeight: 5)
        rainbowCircle.backgroundColor = .clear
        view.addSubview(rainbowCircle)
    }
}

Result

enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • 1
    There are **two methods** to draw a circular gradient: you can (A) draw "lots of small lines" which will do the job pretty well. Or (B) you can "simply" draw every pixel at the exact needed value. This answer correctly implements (A). If you wish to use (B), investigate this QA: https://stackoverflow.com/q/47610272/294884 – Fattie Dec 10 '17 at 18:04
  • @Vasily Bodnarchuk :But this circle is not rotating.How to rot this? – Vork Jul 09 '19 at 09:42
  • **Important! .conic is finally available in 12! It's now one line of code. There is absolutely no need to implement legacy solutions such as explained here! Enjoy** – Fattie Jan 01 '20 at 23:24