Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/ant/2.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
在swift中创建饼图_Swift_Initialization_Pie Chart_Init - Fatal编程技术网

在swift中创建饼图

在swift中创建饼图,swift,initialization,pie-chart,init,Swift,Initialization,Pie Chart,Init,我正在尝试用swift创建一个饼图,并且希望从头开始创建代码,而不是使用第三方扩展 我喜欢它是@IBDesignable的想法,因此我从以下内容开始: import Foundation import UIKit @IBDesignable class PieChart: UIView { var data: Dictionary<String,Int>? required init(coder aDecoder: NSCoder) { super.init(

我正在尝试用swift创建一个饼图,并且希望从头开始创建代码,而不是使用第三方扩展

我喜欢它是@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
  }

}
还是有更好的办法?假设没有办法将数据包含在init函数中


提前谢谢

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

查看标准UI元素(如
UIButton
)以获得设计线索是一件好事。您可以更改
ui按钮的属性,并且它会更新,而无需调用
myButton.setNeedsDisplay()
,因此您应该设计饼图,使其以相同的方式工作

可以使用视图的属性来保存数据。视图应该负责重新绘制自身,因此为
数据
属性定义
didSet
,并在那里调用
setNeedsDisplay()

var data:  Dictionary<String,Int>? {
    didSet {
        // Data changed.  Redraw the view.
        self.setNeedsDisplay()
    }
}
您可以将其扩展到饼图上的其他属性。例如,您可能需要更改背景色。您还需要为该属性定义
didSet
,并调用
setNeedsDisplay

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

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

并且pieChart将只重新绘制一次。

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

因此,是的,您可以在
viewDidLoad
中填充饼图的数据

override func viewDidLoad() {
    super.viewDidLoad()

    pieChart.dataPoints = ...
}
但是,由于此
PieChart
IBDesignable
,这意味着您可能希望在IB中查看饼图的格式副本。因此,您可以在
PieChart
类中实现
prepareforprinterfacebuilder
,提供一些示例数据:

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

    ...
}

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

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++
        }



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

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
}
func字典到排序的数组(dict:dictionary)->数组{
变量元组:数组=数组()
让sortedKeys=(作为NSDictionary记录)。使用选择器(“比较:”)键SortedByValue
用于钥匙插入式钥匙{
tuples.append((key:key as!String,value:dict[key as!String]!)
}
返回元组
}

BTW,我从缺少任何访问控制属性推断,您已经在与视图控制器相同的目标中定义了
PieChart
。通常建议为
@IBDesignable
视图设置一个单独的框架目标。这是一个很棒的答案,Rob,也感谢@vacawama的想法,我从这两个答案中学到了很多。Rob,你说为IBD可设计视图设置一个单独的目标是明智的-这只是为了更方便的代码重用,还是有更重要的原因?@Ben-在WWDC 2014视频中,苹果只是说可设计视图“需要”在一个单独的目标中,但没有清楚地阐明其原理。我可以想象实际的原因(例如,它实际上构建并执行目标),因此,如果你在编写主目标中的代码的中间,当你去IB时,它可能不是“可构建的”;它会更快或更少地建立目标;等等)。虽然我看到它在同一个目标中起作用,但我也看到人们在这样做时遇到了问题。
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
}