Radagast the Brown
Radagast the Brown

Reputation: 397

Ticks around a circle

enter image description here

I am trying to create a custom knob control that has ticks around its dial. The dial will have a dynamic number of ticks (values), so I'll need to draw them. To start, all I want to do is to create a circle with a number of tick marks around its edge. An example image is above.

Here is where I am at currently (image below), and you can see my math must be quite off, and I'm having difficulty getting it to appear correctly. The angles seem very far apart, the positioning of the ticks should be 1/2 way to the right and 1/2 way down. I tried using a ShapeLayer and moving that, but it didn't seem to do anything. Once I get the drawing correct, I'll make it larger so that it appears like the image at the top (clipped by the screen boundary).

All I really want to do at this point is to draw a dynamic number of tics around the edge of a circle. The angle between them can indeed be fixed too. Thanks for your time and attention.

enter image description here

Here is my custom Class currently.

import UIKit

class Disc: UIView {

// Defaults.
private var myOuterRadius: CGFloat = 100.0
private var myInnerRadius: CGFloat = 90.0
private var myNumberOfTicks: Int = 5

override func draw(_ rect: CGRect)
{
    let strokeColor = UIColor.black
    let tickPath = UIBezierPath()
    for i in 0..<myNumberOfTicks {
        let angle = CGFloat(i) * CGFloat(2 * M_PI) / CGFloat(myNumberOfTicks)
        let inner = CGPoint(x: myInnerRadius * cos(angle), y: myInnerRadius * sin(angle))
        let outer = CGPoint(x: myOuterRadius * cos(angle), y: myOuterRadius * sin(angle))
        print(angle, inner, outer)
        tickPath.move(to: inner)
        tickPath.addLine(to: outer)
    }
    tickPath.close()
    tickPath.lineCapStyle = .round
    strokeColor.setStroke()
    tickPath.lineWidth = 2
    tickPath.stroke()
}

init(width: CGFloat, numTicks: Int)
{
    super.init(frame: CGRect(x: 0, y: 0, width: width, height: width))

    myOuterRadius = width / 2
    myInnerRadius = (width / 2) - 10
    myNumberOfTicks = numTicks

    self.backgroundColor = UIColor(netHex: 0xEEEEEE)
    self.layer.cornerRadius = CGFloat(width / 2)
    self.layer.masksToBounds = true
}

required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}

Upvotes: 1

Views: 1580

Answers (3)

rosem
rosem

Reputation: 1378

This would be a good start. There are better ways to draw of course. :p

class ViewController: UIViewController {

var circle: UIView!

override func viewDidLoad() {
    super.viewDidLoad()

    let diameter = 200.0
    let ticks = 100

    circle = UIView(frame: CGRect(x: 0, y: 0, width: diameter, height: diameter))
    circle.center = view.center
    circle.backgroundColor = UIColor.yellow
    circle.layer.cornerRadius = 100.0
    view.addSubview(circle)

    drawTicks(count: ticks)
}

func drawTicks(count: Int) {

    let radius = circle.frame.size.width * 0.5
    var rotationInDegrees: CGFloat = 0

    for i in 0 ..< count {
        let tick = createTick()

        let x = CGFloat(Float(circle.center.x) + Float(radius) * cosf(2 * Float(i) * Float(M_PI) / Float(count) - Float(M_PI) / 2))
        let y = CGFloat(Float(circle.center.y) + Float(radius) * sinf(2 * Float(i) * Float(M_PI) / Float(count) - Float(M_PI) / 2))

        tick.center = CGPoint(x: x, y: y)
        // degress -> radians
        tick.transform = CGAffineTransform.identity.rotated(by: rotationInDegrees * .pi / 180.0)
        view.addSubview(tick)

        rotationInDegrees = rotationInDegrees + (360.0 / CGFloat(count))
    }

}

func createTick() -> UIView {
    let tick = UIView(frame: CGRect(x: 0, y: 0, width: 2.0, height: 10.0))
    tick.backgroundColor = UIColor.blue

    return tick
}

}

Result

Upvotes: 1

Keiwan
Keiwan

Reputation: 8251

As pointed out by vacawama you have to add the position of the circle center to your calculations. You can simply use the center property of UIView for that.

let inner = CGPoint(x: myInnerRadius * cos(angle) + center.x, 
                    y: myInnerRadius * sin(angle) + center.y)
let outer = CGPoint(x: myOuterRadius * cos(angle) + center.x, 
                    y: myOuterRadius * sin(angle) + center.y)

Upvotes: 1

vacawama
vacawama

Reputation: 154583

Your problem is that you are using (0, 0) as the center of your circle.

You need to establish values for the center point of your circle, and then add those offsets to the endpoints of your tick marks.

let centerX: CGFloat = myOuterRadius
let centerY: CGFloat = myOuterRadius

Then use those values when computing inner and outer:

let inner = CGPoint(x: myInnerRadius * cos(angle) + centerX,
    y: myInnerRadius * sin(angle) + centerY)
let outer = CGPoint(x: myOuterRadius * cos(angle) + centerX,
    y: myOuterRadius * sin(angle) + centerY)

Upvotes: 1

Related Questions