在半圆上绘制刻度 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)
然后在p1
和p2
之间画一条线。
注意: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)
}
}
应用程序中的体重秤图片运行:
假设我有一个从单位圆 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)
然后在p1
和p2
之间画一条线。
注意: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)
}
}
应用程序中的体重秤图片运行: