Ios UIBezierPath圆弧用于创建具有圆角和间距的饼图
我想知道我们如何才能创建一个圆形的饼图,如图所示,饼图之间有圆形的边缘和空格 我的第一个方法是:我将馅饼移出其中心点,偏移量=10,使其看起来像照片。但看起来最大的馅饼的半径比较小的小。 然后我改变了半径,但是间距有点奇怪 由于新中心点不在superview的中心,所以它在一侧被截断Ios UIBezierPath圆弧用于创建具有圆角和间距的饼图,ios,swift,uibezierpath,rounded-corners,spacing,Ios,Swift,Uibezierpath,Rounded Corners,Spacing,我想知道我们如何才能创建一个圆形的饼图,如图所示,饼图之间有圆形的边缘和空格 我的第一个方法是:我将馅饼移出其中心点,偏移量=10,使其看起来像照片。但看起来最大的馅饼的半径比较小的小。 然后我改变了半径,但是间距有点奇怪 由于新中心点不在superview的中心,所以它在一侧被截断 outerRadius = outerRadius - offset * 2 * (1 - percentage) (百分比是图表中饼图的比例) 我的第二种方法是:计算每个饼图的中心点,而不是将其移出其原始中心点
outerRadius = outerRadius - offset * 2 * (1 - percentage)
(百分比是图表中饼图的比例)
我的第二种方法是:计算每个饼图的中心点,而不是将其移出其原始中心点。想象有一个空的中间作为一个圆,每个饼的一个新的中心点在这个圆中
大馅饼的问题仍然存在
我尝试的每张幻灯片的新中心点:
let middleAngle = ((startAngle + endAngle) / 2.0).toRadians()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let newCenter = CGPoint(x: center.x + cos(middleAngle) * offset, y: center.y + sin(middleAngle) * offset)
半径和中心点问题|预期结果
这是我的密码
你好吗?如果是这样,我认为你的方法有几个问题。首先,看看你的代码要点,我改变了你的一些做法:
import UIKit
class PieChartView: UIView {
var onTouchPie: ((_ sliceIndex: Int) -> ())?
var shouldHighlightPieOnTouch = false
var shouldShowLabels: Bool = false {
didSet { setNeedsLayout() }
}
var labelTextFont = UIFont.systemFont(ofSize: 12) {
didSet { setNeedsLayout() }
}
var labelTextColor = UIColor.black {
didSet { setNeedsLayout() }
}
var shouldShowTextPercentageFromFieFilledFigures = false {
didSet { setNeedsLayout() }
}
var pieGradientColors: [[UIColor]] = [[.red,.red], [.cyan,.cyan], [.green,.green]] {
didSet { setNeedsLayout() }
}
var pieFilledPercentages:[CGFloat] = [1, 1, 1] {
didSet { setNeedsLayout() }
}
//var segments:[CGFloat] = [40, 30, 30] {
var segments:[CGFloat] = [70, 20, 10] {
didSet { setNeedsLayout() }
}
var offset:CGFloat = 15 {
didSet { setNeedsLayout() }
}
var spaceLineColor: UIColor = .white {
didSet { setNeedsLayout() }
}
private var labels: [UILabel] = []
private var labelSize = CGSize(width: 100, height: 50)
private var shapeLayers = [CAShapeLayer]()
private var gradientLayers = [CAGradientLayer]()
override func layoutSubviews() {
super.layoutSubviews()
labels.forEach({$0.removeFromSuperview()})
labels.removeAll()
shapeLayers.forEach({$0.removeFromSuperlayer()})
shapeLayers.removeAll()
gradientLayers.forEach({$0.removeFromSuperlayer()})
gradientLayers.removeAll()
let valueCount = segments.reduce(CGFloat(0), {$0 + $1})
guard pieFilledPercentages.count >= 3, segments.count >= 3, pieGradientColors.count >= 3 , valueCount > 0 else { return }
let radius = min(bounds.width / 2, bounds.height / 2) * 0.9 //KEN CHANGED
var startAngle: CGFloat = 360
let proportions = segments.map({ ($0 / valueCount * 100).rounded()})
for i in 0..<segments.count {
let endAngle = startAngle - proportions[i] / 100 * 360
let path = createPath(from: startAngle, to: endAngle, oRadius: radius, percentage: proportions[i])
//path.close() //KEN CHANGED
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayers.append(shapeLayer)
let gradientLayer = CAGradientLayer()
gradientLayer.colors = pieGradientColors[i].map({$0.cgColor})
if i == 0 {
gradientLayer.locations = [0.5, 1]
} else {
gradientLayer.locations = [0, 0.5]
}
gradientLayer.mask = shapeLayer
gradientLayer.frame = bounds
if proportions[i] != 0 && pieFilledPercentages[i] != 0 {
layer.addSublayer(gradientLayer)
gradientLayers.append(gradientLayer)
}
let label = labelFromPoint(point: getCenterPointOfArc(startAngle: startAngle, endAngle: endAngle), andText: String(format: "%.f", shouldShowTextPercentageFromFieFilledFigures ? pieFilledPercentages[i] * 100 :segments[i]) + "%")
label.isHidden = !shouldShowLabels
if proportions[i] != 0 {
addSubview(label)
labels.append(label)
}
startAngle = endAngle
}
}
private func labelFromPoint(point: CGPoint, andText text: String) -> UILabel {
let label = UILabel(frame: CGRect(origin: point, size: labelSize))
label.font = labelTextFont
label.textColor = labelTextColor
label.text = text
return label
}
private func getCenterPointOfArc(startAngle: CGFloat, endAngle: CGFloat) -> CGPoint {
let oRadius = max(bounds.width / 2, bounds.height / 2) * 0.8
let center = CGPoint(x: oRadius, y: oRadius)
let centerAngle = ((startAngle + endAngle) / 2.0).toRadians()
let arcCenter = CGPoint(x: center.x + oRadius * cos(centerAngle), y: center.y - oRadius * sin(centerAngle))
return CGPoint(x: (center.x + arcCenter.x) / 2, y: (center.y + arcCenter.y) / 2)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, shouldHighlightPieOnTouch {
shapeLayers.enumerated().forEach { (item) in
if let path = item.element.path, path.contains(touch.location(in: self)) {
item.element.opacity = 1
onTouchPie?(item.offset)
} else {
item.element.opacity = 0.3
}
}
}
super.touchesBegan(touches, with: event)
}
private func highlightLayer(index: Int) {
shapeLayers.enumerated().forEach({$0.element.opacity = $0.offset == index ? 1: 0.3 })
}
private func createPath(from startAngle: CGFloat, to endAngle: CGFloat, oRadius: CGFloat, cornerRadius: CGFloat = 10, percentage: CGFloat) -> UIBezierPath {
let radius: CGFloat = min(bounds.width, bounds.height) / 2.0 - (2.0 * offset)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let midPointAngle = ((startAngle + endAngle) / 2.0).toRadians() //used to spread the segment away from its neighbours after creation
let startAngle = (360.0 - startAngle).toRadians()
let endAngle = (360.0 - endAngle).toRadians()
let circumference: CGFloat = CGFloat(2.0 * (Double.pi * Double(radius)))
let arcLengthPerDegree = circumference / 360.0 //how many pixels long the outer arc is of the pie chart, per 1° of a pie segment
let pieSegmentOuterCornerRadiusInDegrees: CGFloat = 4.0 //for a given segment (and if it's >4° in size), use up 2 of its outer arc's degrees as rounded corners.
let pieSegmentOuterCornerRadius = arcLengthPerDegree * pieSegmentOuterCornerRadiusInDegrees
let path = UIBezierPath()
//move to the centre of the pie chart, offset by the corner radius (so the corner of the segment can be rounded in a bit)
path.move(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * cornerRadius)))
//if the size of the pie segment isn't big enough to warrant rounded outer corners along its outer arc, don't round them off
if ((endAngle - startAngle).toDegrees() <= (pieSegmentOuterCornerRadiusInDegrees * 2.0)) {
//add line from centre of pie chart to 1st outer corner of segment
path.addLine(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * radius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * radius)))
//add arc for segment's outer edge on pie chart
path.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
//move down to the centre of the pie chart, leaving room for rounded corner at the end
path.addLine(to: CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * cornerRadius)))
//add final rounded corner in middle of pie chart
path.addQuadCurve(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * cornerRadius)), controlPoint: center)
} else { //round the corners on the outer arc
//add line from centre of pie chart to circumference of segment, minus the space needed for the rounded corner
path.addLine(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius)), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius))))
//add rounded corner onto start of outer arc
let firstRoundedCornerEndOnArc = CGPoint(x: center.x + (cos(startAngle + pieSegmentOuterCornerRadiusInDegrees.toRadians() - CGFloat(360).toRadians()) * radius), y: center.y + (sin(startAngle + pieSegmentOuterCornerRadiusInDegrees.toRadians() - CGFloat(360).toRadians()) * radius))
path.addQuadCurve(to: firstRoundedCornerEndOnArc, controlPoint: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * radius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * radius)))
//add arc for segment's outer edge on pie chart
path.addArc(withCenter: center, radius: radius, startAngle: startAngle + pieSegmentOuterCornerRadiusInDegrees.toRadians(), endAngle: endAngle - pieSegmentOuterCornerRadiusInDegrees.toRadians(), clockwise: true)
//add rounded corner onto end of outer arc
let secondRoundedCornerEndOnLine = CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius)), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius)))
path.addQuadCurve(to: secondRoundedCornerEndOnLine, controlPoint: CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * radius), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * radius)))
//add line back to centre point of pie chart, leaving room for rounded corner at the end
path.addLine(to: CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * cornerRadius)))
//add final rounded corner in middle of pie chart
path.addQuadCurve(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * cornerRadius)), controlPoint: center)
}
path.close()
//spread the segments out around the pie chart centre
path.apply(CGAffineTransform(translationX: cos(midPointAngle) * offset, y: -sin(midPointAngle) * offset))
return path
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * CGFloat(Double.pi) / 180.0
}
func toDegrees() -> CGFloat {
return self / (CGFloat(Double.pi) / 180.0)
}
}
导入UIKit
类PieChartView:UIView{
var onTouchPie:(((切片索引:Int)->())?
var shouldlhighlightpieontuch=false
var shouldShowLabels:Bool=false{
didSet{setNeedsLayout()}
}
var labelTextFont=UIFont.systemFont(大小:12){
didSet{setNeedsLayout()}
}
var labelTextColor=UIColor.black{
didSet{setNeedsLayout()}
}
var应显示FiefiledFigures的文本百分比=false{
didSet{setNeedsLayout()}
}
var PiegradientColor:[[UIColor]=[[0.red、.red]、.cyan、.cyan]、.green、.green]]{
didSet{setNeedsLayout()}
}
var pieFilledPercentages:[CGFloat]=[1,1,1]{
didSet{setNeedsLayout()}
}
//变量段:[CGFloat]=[40,30,30]{
变量段:[CGFloat]=[70,20,10]{
didSet{setNeedsLayout()}
}
变量偏移量:CGFloat=15{
didSet{setNeedsLayout()}
}
var spaceLineColor:UIColor=.white{
didSet{setNeedsLayout()}
}
专用变量标签:[UILabel]=[]
私有变量labelSize=CGSize(宽:100,高:50)
私有变量shapeLayers=[CAShapeLayer]()
私有变量梯度层=[CAGradientLayer]()
覆盖func布局子视图(){
super.layoutSubviews()
labels.forEach({$0.removeFromSuperview()})
labels.removeAll()
forEach({$0.removeFromSuperlayer()})
shapeLayers.removeAll()
forEach({$0.removeFromSuperlayer()})