Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/96.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios UIBezierPath圆弧用于创建具有圆角和间距的饼图_Ios_Swift_Uibezierpath_Rounded Corners_Spacing - Fatal编程技术网

Ios UIBezierPath圆弧用于创建具有圆角和间距的饼图

Ios UIBezierPath圆弧用于创建具有圆角和间距的饼图,ios,swift,uibezierpath,rounded-corners,spacing,Ios,Swift,Uibezierpath,Rounded Corners,Spacing,我想知道我们如何才能创建一个圆形的饼图,如图所示,饼图之间有圆形的边缘和空格 我的第一个方法是:我将馅饼移出其中心点,偏移量=10,使其看起来像照片。但看起来最大的馅饼的半径比较小的小。 然后我改变了半径,但是间距有点奇怪 由于新中心点不在superview的中心,所以它在一侧被截断 outerRadius = outerRadius - offset * 2 * (1 - percentage) (百分比是图表中饼图的比例) 我的第二种方法是:计算每个饼图的中心点,而不是将其移出其原始中心点

我想知道我们如何才能创建一个圆形的饼图,如图所示,饼图之间有圆形的边缘和空格

我的第一个方法是:我将馅饼移出其中心点,偏移量=10,使其看起来像照片。但看起来最大的馅饼的半径比较小的小。 然后我改变了半径,但是间距有点奇怪 由于新中心点不在superview的中心,所以它在一侧被截断

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)
半径和中心点问题|预期结果

这是我的密码

你好吗?如果是这样,我认为你的方法有几个问题。首先,看看你的代码要点,我改变了你的一些做法:

  • 更改了饼图分段的大小(以便我可以测试>180°分段)和颜色
  • 我在CGFloat扩展中添加了一个方便的toRadians()函数(与您已经添加的toRadians()函数正好相反)
  • 我将radius变量更改为边界宽度/高度的最小值(而不是像您所做的那样最大值),以便它在不进行裁剪的情况下适合视图。这只是个人偏好&不会改变代码的整体功能(例如,您可能需要更大、可滚动,而我只是想调试这个特定的问题)。我还添加了填充,这样当分段被隔开时,它仍然适合分段
  • 我按照你解决问题的原始路线走;在饼图的中心绘制所有线段,然后将它们隔开,而不是试图将每个线段画离中心。尽管在构造它们时将它们保持在中心位置更简单,并且导致更可读的代码,但您可以选择这两种方法。通过createPath:函数结尾处的仿射变换实现向外间隔,该函数通过给定线段的中间角将它们向外间隔。你可能想在现实生活中更聪明一点(这有点原始),因为根据屏幕截图,非常大的片段看起来会比小片段彼此分开得更远(红色片段看起来比绿色和蓝色彼此分开得更远)。因此,您可能希望开发一种算法,该算法不仅包含线段的中间角,还包含线段的大小,以便不仅确定方向,还确定分隔线段的距离?或者,在确定分离方向时,可能会考虑段的相邻中间角?个人品味
  • 在layoutSubviews()中,为每个段提供了不同的oRadius。这就是为什么线段的半径彼此不同。我只是为他们提供了“半径”。如果在我的createPath()函数中注释掉仿射变换(将它们隔开),您将看到我版本中的线段都是相同大小的半径
  • 我将path.close()移到createPath()函数中,而不是在调用它之后。看起来更整洁
  • 就绘制给定的线段而言,我采取了完全不同的方法(除了在饼图中居中绘制线段,然后再移动)。我用两条直线和一条圆弧画了它,作为饼图的外周长。对于圆角,我使用了二次Bézier曲线,而不是绘制圆弧(注意:线段的中心圆角绘制不正确,导致奇怪的图形伪影)。这些只需要1个控制点,而不是像三次Bézier曲线那样需要2个控制点。因此,您可以将线段的角指定为该控制点,它将为您提供一个圆角,该圆角适合您要圆角的三角形的角。因此,我只在每个角点附近绘制直线/圆弧,然后绘制一条四次Bézier曲线以绕过角点,然后继续绘制线段的其余部分
  • 让我知道如果有什么需要澄清,希望这有帮助

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