Swift自定义饼图-从多个UIBezier路径切割透明圆的奇怪行为
使用Swift创建自定义饼图/油炸圈饼样式图时,我在尝试从油炸圈饼上切出洞时遇到了一个奇怪的问题。我尝试了第二个UIBezierPath的圆心和半径的变化,但是我没有能够从圆心完成一个干净的孔。任何帮助都将不胜感激 UIView的子类:Swift自定义饼图-从多个UIBezier路径切割透明圆的奇怪行为,swift,uikit,Swift,Uikit,使用Swift创建自定义饼图/油炸圈饼样式图时,我在尝试从油炸圈饼上切出洞时遇到了一个奇怪的问题。我尝试了第二个UIBezierPath的圆心和半径的变化,但是我没有能够从圆心完成一个干净的孔。任何帮助都将不胜感激 UIView的子类: import UIKit public class DoughnutView: UIView { public var data: [Float]? { didSet { setNeedsDisplay() } } public var color
import UIKit
public class DoughnutView: UIView {
public var data: [Float]? {
didSet { setNeedsDisplay() }
}
public var colors: [UIColor]? {
didSet { setNeedsDisplay() }
}
@IBInspectable public var spacerWidth: CGFloat = 2 {
didSet { setNeedsDisplay() }
}
@IBInspectable public var thickness: CGFloat = 20 {
didSet { setNeedsDisplay() }
}
public override func draw(_ rect: CGRect) {
guard data != nil && colors != nil else {
return
}
guard data?.count == colors?.count else {
return
}
let center = CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
let radius = min(bounds.size.width, bounds.size.height) / 2.0
let total = data?.reduce(Float(0)) { $0 + $1 }
var startAngle = CGFloat(Float.pi)
UIColor.clear.setStroke()
for (key, value) in data!.enumerated() {
let endAngle = startAngle + CGFloat(2.0 * Float.pi) * CGFloat(value / total!)
let doughnut = UIBezierPath()
doughnut.move(to: center)
doughnut.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let hole = UIBezierPath(arcCenter: center, radius: radius - thickness, startAngle: startAngle, endAngle: endAngle, clockwise: true)
hole.move(to: center)
doughnut.append(hole)
doughnut.usesEvenOddFillRule = true
doughnut.close()
doughnut.lineWidth = spacerWidth
colors?[key].setFill()
doughnut.fill()
doughnut.stroke()
startAngle = endAngle
}
}
public override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = UIColor.clear
setNeedsDisplay()
}
}
然后是ViewController:
class ViewController: UIViewController {
@IBOutlet weak var doughnut: DoughnutView!
override func viewDidLoad() {
super.viewDidLoad()
doughnut.data = [3, 14, 5]
doughnut.colors = [UIColor.red, UIColor.yellow, UIColor.blue]
view.backgroundColor = .purple
}
}
结果是:
所以你想要这个:
问题是addArc
创建的是弧,而不是楔块。也就是说,它创建了一条跟踪圆的一部分的路径,而没有径向线段进入或离开圆的中心。由于您没有仔细添加这些径向线段,因此在调用close()
时,您会在不需要它们的地方得到直线
我猜您正试图通过move(to:)
调用添加这些径向段,但您还没有完成所有必要的工作
无论如何,这可以做得更简单。从跟踪切片外边缘的圆弧开始,然后添加一条沿相反方向跟踪切片内边缘的圆弧UIBezierPath
将自动用一条直线将第一条弧的末端连接到第二条弧的起点,而close()
将用另一条直线将第二条弧的末端连接到第一条弧的起点。因此:
let slice = UIBezierPath()
slice.addArc(withCenter: center, radius: radius,
startAngle: startAngle, endAngle: endAngle, clockwise: true)
slice.addArc(withCenter: center, radius: radius - thickness,
startAngle: endAngle, endAngle: startAngle, clockwise: false)
slice.close()
也就是说,我们可以通过其他一些方式改进您的draw(:)
方法:
- 我们可以使用
将guard
数据和
颜色重新绑定到非可选项
- 我们还可以
保护
数据不为空
- 我们可以将
减少半径
,以避免剪切笔划边界。(您将问题代码中的笔划颜色更改为间隔宽度
,但图像显示为.clear
).white
- 我们可以统一使用
来减少转换CGFloat
- 我们可以将
除以2π一次,而不是将每个total
乘以2πvalue
- 我们可以
压缩成一个序列,而不是使用将(颜色、数据)
和订阅枚举()
颜色
罗布,这是一个很好的回答。感谢您不仅为这个问题提供了一个漂亮的解决方案,还感谢您的解释,以及关于如何改进守卫声明、铸造和几何数学的建议。这是迄今为止我收到的关于StackOverflow的最好答案。
public override func draw(_ rect: CGRect) {
guard
let data = data, !data.isEmpty,
let colors = colors, data.count == colors.count
else { return }
let center = CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
let radius = min(bounds.size.width, bounds.size.height) / 2.0 - spacerWidth
let total: CGFloat = data.reduce(0) { $0 + CGFloat($1) } / (2 * .pi)
var startAngle = CGFloat.pi
UIColor.white.setStroke()
for (color, value) in zip(colors, data) {
let endAngle = startAngle + CGFloat(value) / total
let slice = UIBezierPath()
slice.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
slice.addArc(withCenter: center, radius: radius - thickness, startAngle: endAngle, endAngle: startAngle, clockwise: false)
slice.close()
color.setFill()
slice.fill()
slice.lineWidth = spacerWidth
slice.stroke()
startAngle = endAngle
}
}