【问题标题】:Creating a pie chart in swift快速创建饼图
【发布时间】:2016-02-29 21:24:42
【问题描述】:

我正在尝试快速创建饼图,并且希望从头开始创建代码,而不是使用 3rd 方扩展。

我喜欢它是 @IBDesignable 的想法,所以我从这个开始:

import Foundation
import UIKit

@IBDesignable class PieChart: UIView {

  var data:  Dictionary<String,Int>?

  required init(coder aDecoder: NSCoder) {
    super.init(coder:aDecoder)!
    self.contentMode = .Redraw
  }

  override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.clearColor()
    self.contentMode = .Redraw
  }

  override fun drawRect(rect: CGRect) {
    // draw the chart in here
  }

}

我不确定如何最好地将数据放入图表中。我应该有这样的东西吗:

@IBOutlet weak var pieChart: PieChart!
override func viewDidLoad() {
    pieChart.data = pieData
    pieChart.setNeedsDisplay()
}

或者有更好的方法吗?想必是没有办法在init函数中包含数据吧?

提前致谢!

【问题讨论】:

  • 顺便说一句,我推断您在与视图控制器相同的目标中定义了 PieChart 的任何访问控制属性。通常建议为@IBDesignable 视图设置单独的框架目标。

标签: swift initialization pie-chart init


【解决方案1】:

您可以创建一个包含数据的便利 init,但这只有在您从代码创建视图时才有用。如果您的视图被添加到情节提要中,您将需要一种在视图创建后设置数据的方法。

最好查看标准 UI 元素(如 UIButton)以获取设计线索。您可以更改 UIButton 的属性,并且无需调用 myButton.setNeedsDisplay() 即可更新,因此您应该设计饼图以以相同的方式工作。

拥有保存数据的视图属性很好。视图应负责重绘自身,因此为您的 data 属性定义 didSet 并在此处调用 setNeedsDisplay()

var data:  Dictionary<String,Int>? {
    didSet {
        // Data changed.  Redraw the view.
        self.setNeedsDisplay()
    }
}

然后你可以简单的设置数据,饼图会重绘:

pieChart.data = pieData

您可以将此扩展到饼图上的其他属性。例如,您可能想要更改背景颜色。您还可以为该属性定义 didSet 并调用 setNeedsDisplay

请注意,setNeedsDisplay 只是设置了一个标志,稍后将绘制视图。多次调用setNeedsDisplay 不会导致您的视图多次重绘,因此您可以执行以下操作:

pieChart.data = pieData
pieChart.backgroundColor = .redColor()
pieChart.draw3D = true  // draw the pie chart in 3D

饼图只会重绘一次。

【讨论】:

    【解决方案2】:

    不,如果您已将其添加到情节提要的场景中,则无法在 init 方法中设置数据(因为将调用 init(coder:))。

    所以,是的,您可以在viewDidLoad 中填充饼图的数据。

    override func viewDidLoad() {
        super.viewDidLoad()
    
        pieChart.dataPoints = ...
    }
    

    但是,因为PieChartIBDesignable,这意味着您可能想在 IB 中查看饼图的再现。所以你可以在PieChart 类中实现prepareForInterfaceBuilder,提供一些示例数据:

    override public func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        dataPoints = ...
    }
    

    这样您现在可以在 Interface Builder 中享受可设计的视图(例如,查看预览;其他可检查的属性可以显示)。预览图是我们的示例数据,不是运行时会显示的数据,但可能足以欣赏整体设计:

    而且,正如 vacawama 所说,您希望将 setNeedsDisplay 移动到属性的 didSet 观察者中。

    public class PieChart: UIView {
    
        public var dataPoints: [DataPoint]? {          // use whatever type that makes sense for your app, though I'd suggest an array (which is ordered) rather than a dictionary (which isn't)
            didSet { setNeedsDisplay() }
        }
    
        @IBInspectable public var lineWidth: CGFloat = 2 {
            didSet { setNeedsDisplay() }
        }
    
        ...
    }
    

    【讨论】:

    • 一个很棒的答案 Rob,也感谢 @vacawama 的想法,我从这两个答案中学到了很多东西。 Rob,您说为 IBDesignable 视图设置一个单独的目标是可取的——这只是为了更容易地重用代码,还是有更重要的原因?
    • @Ben - 在 WWDC 2014 视频What's New in Interface Builder 中,Apple 只是说可设计视图“需要”在一个单独的目标中,但没有清楚地阐明理由。我可以想象实际的原因(例如,它实际上构建并执行了目标,所以如果你正在编写主要目标中的代码,那么当你去 IB 时它可能不是“可构建的”;它将构建该目标更多快速或不那么频繁;等等)。不过,我已经看到它在同一个目标中起作用,但我也看到人们在这样做时遇到了问题。
    【解决方案3】:

    以防万一有人再次看到这个问题,我想发布我完成的代码,以便对其他人有用。这里是:

    import Foundation
    import UIKit
    
    @IBDesignable class PieChart: UIView {
        var dataPoints: Dictionary<String,Double> = ["Alpha":1,"Beta":2,"Charlie":3,"Delta":4,"Echo":2.5,"Foxtrot":1.4] {
            didSet { setNeedsDisplay() }
        }
        @IBInspectable var lineWidth: CGFloat = 1.0 {
            didSet { setNeedsDisplay()
            }
        }
        @IBInspectable var lineColor: UIColor = uicolor_normal {
            didSet { setNeedsDisplay() }
        }
    
        required init(coder aDecoder: NSCoder) {
            super.init(coder:aDecoder)!
            self.contentMode = .Redraw
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.backgroundColor = UIColor.clearColor()
            self.contentMode = .Redraw
        }
    
        override func drawRect(rect: CGRect) {
    
    
            // set font for labels
            let fieldColor: UIColor = UIColor.darkGrayColor()
            let fieldFont = uifont_piechartkey
            var fieldAttributes: NSDictionary = [
                NSForegroundColorAttributeName: fieldColor,
                NSFontAttributeName: fieldFont!
            ]
    
            // get the graphics context and prepare an inset box for the pie
            let ctx = UIGraphicsGetCurrentContext()
            let margin: CGFloat = lineWidth
            let box0 = CGRectInset(self.bounds, margin, margin)
            let keyHeight = CGFloat( ceil( Double(dataPoints.count) / 3.0) * 24 ) + 16
            let side : CGFloat = min(box0.width, box0.height-keyHeight)
            let box = CGRectMake((self.bounds.width-side)/2, (self.bounds.height-side-keyHeight)/2,side,side)
            let radius : CGFloat = min(box.width, box.height)/2.0
    
    
            // converts percentages to radians for drawing the segment
            func percent_to_rad(p: Double) -> CGFloat {
                let rad = CGFloat(p * 0.02 * M_PI)
                return rad
            }
    
            // draws a segment
            func draw_arc(start: CGFloat, end: CGFloat, color: CGColor) {
                CGContextBeginPath(ctx)
                CGContextMoveToPoint(ctx, box.midX, box.midY)
                CGContextSetFillColorWithColor(ctx, color)
                CGContextAddArc(ctx,box.midX,box.midY,radius-lineWidth/2,start,end,0)
                CGContextClosePath(ctx)
                CGContextFillPath(ctx)
            }
            // draws a key item
            func draw_key(keyName: String, keyValue: Double, color: CGColor, keyX: CGFloat, keyY: CGFloat) {
                CGContextBeginPath(ctx)
                CGContextMoveToPoint(ctx, keyX, keyY)
                CGContextSetFillColorWithColor(ctx, color)
                CGContextAddArc(ctx,keyX,keyY,8,0,CGFloat(2 * M_PI),0)
                CGContextClosePath(ctx)
                CGContextFillPath(ctx)
                keyName.drawInRect(CGRectMake(keyX + 12,keyY-8,self.bounds.width/3,16),withAttributes: fieldAttributes as? [String : AnyObject])
            }
    
    
            let total =  Double(dataPoints.values.reduce(0, combine: +)) // the total of all values
            // convert dictionary to sorted touples
            let dataPointsArray = dictionary_to_sorted_array(dataPoints)
    
            // now sort the dictionary into an Array
            var start = -CGFloat(M_PI_2) // start at 0 degrees, not 90
            var end: CGFloat
            var i = 0
    
            // draw all segments
            for dataPoint in dataPointsArray {
                end = percent_to_rad(Double( (dataPoint.value)/total) * 100 )+start
                draw_arc(start,end:end,color: uicolors_chart[i%uicolors_chart.count].CGColor)
                start = end
                i++
            }
            // the key
            var keyX = self.bounds.minX + 8
            var keyY = self.bounds.height - keyHeight + 32
                i = 0
            for dataPoint in dataPointsArray {
                draw_key(dataPoint.key, keyValue: dataPoint.value, color: uicolors_chart[i%uicolors_chart.count].CGColor, keyX: keyX, keyY: keyY)
                if((i+1)%3 == 0) {
                    keyX = self.bounds.minX + 8
                    keyY += 24
                } else {
                    keyX += self.bounds.width / 3
                }
                i++
            }
    
    
    
        }
    }
    

    这将创建一个饼图,如下所示:

    [

    您需要的其他代码是颜色数组:

    let uicolor_chart_1 = UIColor.init(red: 0.0/255, green:153.0/255, blue:255.0/255, alpha:1.0)  //16b
    let uicolor_chart_2 = UIColor.init(red: 0.0/255, green:200.0/255, blue:120.0/255, alpha:1.0)
    let uicolor_chart_3 = UIColor.init(red: 140.0/255, green:220.0/255, blue:0.0/255, alpha:1.0)
    let uicolor_chart_4 = UIColor.init(red: 255.0/255, green:240.0/255, blue:0.0/255, alpha:1.0)
    let uicolor_chart_5 = UIColor.init(red: 255.0/255, green:180.0/255, blue:60.0/255, alpha:1.0)
    let uicolor_chart_6 = UIColor.init(red: 235.0/255, green:60.0/255, blue:150.0/255, alpha:1.0)
    let uicolors_chart : [UIColor] = [uicolor_chart_1,uicolor_chart_2,uicolor_chart_3,uicolor_chart_4,uicolor_chart_5,uicolor_chart_6]
    

    以及将字典转换为数组的代码:

    func dictionary_to_sorted_array(dict:Dictionary<String,Double>) ->Array<(key:String,value:Double)> {
        var tuples: Array<(key:String,value:Double)> = Array()
        let sortedKeys = (dict as NSDictionary).keysSortedByValueUsingSelector("compare:")
        for key in sortedKeys {
            tuples.append((key:key as! String,value:dict[key as! String]!))
        }
        return tuples
    }
    

    【讨论】:

      猜你喜欢
      • 2015-04-30
      • 2023-04-02
      • 1970-01-01
      • 2020-02-23
      • 1970-01-01
      • 1970-01-01
      • 2014-11-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多