简体   繁体   中英

Drawing a scale on semi-circle Swift/UIView

Imagine I am having a full semi-circle from 0 to Pi from the unit circle. There is a small number on the left side named min and a big number on the right side called max. There are both interchangeable inside the app depending on some factors. Does anybody of you have a nice idea on how to draw a scale like I did in the drawing below? I would like to have longer lines for every x mod 10 = 0 and three larger ones in between. The grey circle is just for orientation.

So I started with the following piece of code:

let radius = CGFloat(40)
let dashLong = CGFloat(10)
let dashShort = CGFloat(5)
let middle = CGPoint(x: 50, y: 50)
let leftAngle = CGFloat(Double.pi)
let rightAngle = CGFloat(0)
let min = 45 //random num
let max = 117 //random num



let innerPath = UIBezierPath(arcCenter: middle, radius: radius, startAngle: rightAngle, endAngle: leftAngle, clockwise: true)
let middlePath = UIBezierPath(arcCenter: middle, radius: radius+dashShort, startAngle: rightAngle, endAngle: leftAngle, clockwise: true)
let outerPath = UIBezierPath(arcCenter: middle, radius: radius+dashLong, startAngle: rightAngle, endAngle: leftAngle, clockwise: true)

So there is a radius and also the length of the two types of dashes in the scale. I chose 45 and 117 as random integers for the extrem values of the scale. My three paths which do not need to be drawn are just an orientation on where the dashes need to be started and ended on. So for 50,60,...110 there start at the innerPath and go to the outer one, I am pretty sure that must be in the same angle for a dash on all circles.

Does anyone has a very smart idea how to continue this to calc the dashes and draw them without getting messed up code?

在此处输入图片说明

我的建议是在CALayer中绘制此半圆,并在不同的CALayer中从半圆的中心绘制线,然后对它们都进行遮罩,以使其看起来像这样

Here's the math for drawing a tick mark.

Let's do everything as CGFloat to keep the conversions to a minimum:

let radius: CGFloat = 40
let dashLong: CGFloat = 10
let dashShort: CGFloat 5
let middle = CGPoint(x: 50, y: 50)
let leftAngle: CGFloat = .pi
let rightAngle: CGFloat = 0
let min: CGFloat = 45 //random num
let max: CGFloat = 117 //random num

First, compute your angle .

let value: CGFloat = 50
let angle = (max - value)/(max - min) * .pi

Now compute your two points:

let p1 = CGPoint(x: middle.x + cos(angle) * radius,
                 y: middle.y - sin(angle) * radius)

// use dashLong for a long tick, and dashShort for a short tick
let radius2 = radius + dashLong

let p2 = CGPoint(x: middle.x + cos(angle) * radius2,
                 y: middle.y - sin(angle) * radius2)

Then draw a line between p1 and p2 .

Note: In iOS, the coordinate system is upside down with +Y being down, which is why the sin calculation is subtracted from middle.y .


Complete Example

enum TickStyle {
    case short
    case long
}

class ScaleView: UIView {
    // ScaleView properties.  If any are changed, redraw the view
    var radius: CGFloat = 40           { didSet { self.setNeedsDisplay() } }
    var dashLong: CGFloat = 10         { didSet { self.setNeedsDisplay() } }
    var dashShort: CGFloat = 5         { didSet { self.setNeedsDisplay() } }
    var middle = CGPoint(x: 50, y: 50) { didSet { self.setNeedsDisplay() } }
    var leftAngle: CGFloat = .pi       { didSet { self.setNeedsDisplay() } }
    var rightAngle: CGFloat = 0        { didSet { self.setNeedsDisplay() } }
    var min: CGFloat = 45              { didSet { self.setNeedsDisplay() } }
    var max: CGFloat = 117             { didSet { self.setNeedsDisplay() } }

    override func draw(_ rect: CGRect) {
        let path = UIBezierPath()

        // draw the arc
        path.move(to: CGPoint(x: middle.x - radius, y: middle.y))
        path.addArc(withCenter: middle, radius: radius, startAngle: leftAngle, endAngle: rightAngle, clockwise: true)

        let startTick = ceil(min / 2.5) * 2.5
        let endTick = floor(max / 2.5) * 2.5

        // add tick marks every 2.5 units
        for value in stride(from: startTick, through: endTick, by: 2.5) {
            let style: TickStyle = value.truncatingRemainder(dividingBy: 10) == 0 ? .long : .short
            addTick(at: value, style: style, to: path)
        }

        // stroke the path
        UIColor.black.setStroke()
        path.stroke()
    }

    // add a tick mark at value with style to path
    func addTick(at value: CGFloat, style: TickStyle, to path: UIBezierPath) {
        let angle = (max - value)/(max - min) * .pi

        let p1 = CGPoint(x: middle.x + cos(angle) * radius,
                         y: middle.y - sin(angle) * radius)

        var radius2 = radius
        if style == .short {
            radius2 += dashShort
        } else if style == .long {
            radius2 += dashLong
        }

        let p2 = CGPoint(x: middle.x + cos(angle) * radius2,
                         y: middle.y - sin(angle) * radius2)

        path.move(to: p1)
        path.addLine(to: p2)
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let view = ScaleView(frame: CGRect(x: 50, y: 50, width: 100, height: 60))
        view.backgroundColor = .yellow
        self.view.addSubview(view)
    }
}

Picture of scale running in app:

在应用程序中运行的秤的图片

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM