在半圆上绘制刻度 Swift/UIView

Drawing a scale on semi-circle Swift/UIView

假设我有一个从单位圆 0 到 Pi 的完整半圆。左边有一个小数 min,右边有一个大数 max。根据某些因素,应用程序内部可以互换。 你们中有人知道如何像我在下图中那样绘制比例尺吗?我希望每个 x mod 10 = 0 都有更长的行,中间有三个更大的行。灰色圆圈只是为了定位。

所以我从以下代码开始:

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)

所以比例尺中有半径和两种破折号的长度。我选择 45 和 117 作为标度极值的随机整数。我不需要绘制的三个路径只是破折号需要开始和结束的方向。所以对于 50,60,...110,从内部路径开始到外部路径,我很确定所有圆圈上的破折号必须处于相同的角度。

有没有人有一个非常聪明的想法如何继续计算破折号并绘制它们而不会弄乱代码?

我的建议是在 CALayer 中画这个半圆,在不同的 CALayer 中从半圆的中心画线,并遮住它们,这样看起来像这样

这是绘制刻度线的数学运算。

让我们尽一切努力 CGFloat 以将转化率保持在最低水平:

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

首先,计算您的 angle

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

现在计算你的两点:

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)

然后在p1p2之间画一条线。

注意:iOS中坐标系倒置,+Y向下,所以sin的计算要减去middle.y


完整示例

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)
    }
}

应用程序中的体重秤图片运行: