跟刚才所做的一样,使用Interface Builder添加管理视图。
回到Main.storyboard中,选择BarGraphViewController场景。
把一个新的UIView拖到视图中,将class改为CPTGraphHostingView,将outlet连接到view controller的hostView对象上。
在Utilities\Size Inspector(尺子状的标签)中调整成如下尺寸: X = 0, Y = 53, Width = 600, Height = 547
添加四条边的约束,确保Constrain to margins没有被勾选。
最后设置任意的背景色,仍然使用的是透明度为92%的灰度色。
UI组件都弄好了,现在来绘制条形图吧。
首先回到BarGraphViewController中,需要添加几个常量属性。在其他属性后添加如下属性:
let BarWidth = 0.25
let BarInitialX = 0.25复制代码
来需要一个辅助函数来计算最高汇率,在updateLabels()方法后添加如下函数:
func highestRateValue() -> Double {
var maxRate = DBL_MIN
for rate in rates {
maxRate = max(maxRate, rate.maxRate().doubleValue)
return maxRate
}复制代码
接着在highestRateValue()后添加如下方法:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
initPlot()
func initPlot() {
configureHostView()
configureGraph()
configureChart()
configureAxes()
func configureHostView() {
func configureGraph() {
func configureChart() {
func configureAxes() {
}复制代码
是不是有点眼熟? 对的,这个结构跟之前一样。
在configureHostView()方法中添加如下:
hostView.allowPinchScaling = false复制代码
这次也不需要捏合缩放,禁用它。
在configureGraph()方法中添加如下代码:
// 1 - 新建graph
let graph = CPTXYGraph(frame: hostView.bounds)
graph.plotAreaFrame?.masksToBorder = false
hostView.hostedGraph = graph
// 2 - 设置graph
graph.apply(CPTTheme(named: CPTThemeName.plainWhiteTheme))
graph.fill = CPTFill(color: CPTColor.clear())
graph.paddingBottom = 30.0
graph.paddingLeft = 30.0
graph.paddingTop = 0.0
graph.paddingRight = 0.0
// 3 - 设置样式
let titleStyle = CPTMutableTextStyle()
titleStyle.color = CPTColor.black()
titleStyle.fontName = "HelveticaNeue-Bold"
titleStyle.fontSize = 16.0
titleStyle.textAlignment = .center
graph.titleTextStyle = titleStyle
let title = "\(base.name) exchange rates\n\(rates.first!.date) - \(rates.last!.date)"
graph.title = title
graph.titlePlotAreaFrameAnchor = .top
graph.titleDisplacement = CGPoint(x: 0.0, y: -16.0)
// 4 - 设置绘制区域
let xMin = 0.0
let xMax = Double(rates.count)
let yMin = 0.0
let yMax = 1.4 * highestRateValue()
guard let plotSpace = graph.defaultPlotSpace as? CPTXYPlotSpace else { return }
plotSpace.xRange = CPTPlotRange(locationDecimal: CPTDecimalFromDouble(xMin), lengthDecimal: CPTDecimalFromDouble(xMax - xMin))
plotSpace.yRange = CPTPlotRange(locationDecimal: CPTDecimalFromDouble(yMin), lengthDecimal: CPTDecimalFromDouble(yMax - yMin))复制代码
分析下上面的代码:
首先实例化一个CPTXYGraph对象,其实就是一个条形图,将其与hostView连接起来。
接着声明默认的主题为空白主题,设置上侧与左侧的内边距给坐标轴留出空间。
设置文本样式,表格标题与标题位置。
最后设置CPTXYPlotSpace,它负责将设备的坐标系映射到graph的坐标系。
在这个graph中需要在同一块区域内绘制出三种汇率,当然,也可以分别在不同区域进行绘制。
在绘图区域中会假定一个汇率的区间范围,在之后会看到如何在事先不知道这个范围的情况下让绘图区域的尺寸自动调节。
graph搞定后,是时候添加一些条形图了,在configureChart()方法中添加如下代码:
// 1 - 设置三个条形图
plot1 = CPTBarPlot()
plot1.fill = CPTFill(color: CPTColor(componentRed:0.92, green:0.28, blue:0.25, alpha:1.00))
plot2 = CPTBarPlot()
plot2.fill = CPTFill(color: CPTColor(componentRed:0.06, green:0.80, blue:0.48, alpha:1.00))
plot3 = CPTBarPlot()
plot3.fill = CPTFill(color: CPTColor(componentRed:0.22, green:0.33, blue:0.49, alpha:1.00))
// 2 - 设置线条样式
let barLineStyle = CPTMutableLineStyle()
barLineStyle.lineColor = CPTColor.lightGray()
barLineStyle.lineWidth = 0.5
// 3 - 将条形添加到graph
guard let graph = hostView.hostedGraph else { return }
var barX = BarInitialX
let plots = [plot1!, plot2!, plot3!]
for plot: CPTBarPlot in plots {
plot.dataSource = self
plot.delegate = self
plot.barWidth = NSNumber(value: BarWidth)
plot.barOffset = NSNumber(value: barX)
plot.lineStyle = barLineStyle
graph.add(plot, to: graph.defaultPlotSpace)
barX += BarWidth
}复制代码
上面的代码做了什么:
实例化每个条形图并设置填充颜色。
实例化一个CPTMutableLineStyle对象,它表示每个条形图外边界的样式。
给每个条形图进行一个公共的配置,包含数据源与委托、每个图的宽度与相对距离、线条样式,最后将图形添加到graph中。
现在还看不到条形图,先构建一下app确保所有代码都能编译成功。
为了在条形图中显示数据,需要实现委托方法来给graph提供必要的数据。
使用如下代码替换numberOfRecordsForPlot(for:)中的内容:
return UInt(rates.count)复制代码
这个方法会返回要显示的记录个数。
使用如下代码替换numberForPlot(for:field:record:)中的内容:
if fieldEnum == UInt(CPTBarPlotField.barTip.rawValue) {
if plot == plot1 {
return 1.0 as AnyObject?
if plot == plot2 {
return rates[Int(idx)].rates[symbols[0].name]!
if plot == plot3 {
return rates[Int(idx)].rates[symbols[1].name]!
return idx复制代码
CPTBarPlotField.BarTip属性表示条形图的相对尺寸,使用已有的属性进行检索得到所需的汇率数据,idx值为汇率的索引值。
构建并运行,应该会看到下面这样:
快成功了! 还有件事没做,你会注意到图中并没有坐标轴的标识信息。
在configureAxes()方法中添加如下代码来解决这个问题:
// 1 - 设置样式
let axisLineStyle = CPTMutableLineStyle()
axisLineStyle.lineWidth = 2.0
axisLineStyle.lineColor = CPTColor.black()
// 2 - 获取graph的轴线集
guard let axisSet = hostView.hostedGraph?.axisSet as? CPTXYAxisSet else { return }
// 3 - 设置x轴
if let xAxis = axisSet.xAxis {
xAxis.labelingPolicy = .none
xAxis.majorIntervalLength = 1
xAxis.axisLineStyle = axisLineStyle
var majorTickLocations = Set<NSNumber>()
var axisLabels = Set<CPTAxisLabel>()
for (idx, rate) in rates.enumerated() {
majorTickLocations.insert(NSNumber(value: idx))
let label = CPTAxisLabel(text: "\(rate.date)", textStyle: CPTTextStyle())
label.tickLocation = NSNumber(value: idx)
label.offset = 5.0
label.alignment = .left
axisLabels.insert(label)
xAxis.majorTickLocations = majorTickLocations
xAxis.axisLabels = axisLabels
// 4 - 设置y轴
if let yAxis = axisSet.yAxis {
yAxis.labelingPolicy = .fixedInterval
yAxis.labelOffset = -10.0
yAxis.minorTicksPerInterval = 3
yAxis.majorTickLength = 30
let majorTickLineStyle = CPTMutableLineStyle()
majorTickLineStyle.lineColor = CPTColor.black().withAlphaComponent(0.1)
yAxis.majorTickLineStyle = majorTickLineStyle
yAxis.minorTickLength = 20
let minorTickLineStyle = CPTMutableLineStyle()
minorTickLineStyle.lineColor = CPTColor.black().withAlphaComponent(0.05)
yAxis.minorTickLineStyle = minorTickLineStyle
yAxis.axisLineStyle = axisLineStyle
}复制代码
简单的说,上面的代码首先定义了坐标轴线条与标签的样式,接着通过检索得到graph中的坐标轴集合,设置x轴与y轴。
构建并运行一下看看变化。
的确更好,不是吗? 唯一的缺点就是坐标轴信息是空白的————没有给予任何关于汇率的准确信息。
可以这样来弥补这个问题:当用户点击每个条形图中的粗线条时显示具体的数据,为此需要增加一个属性:
var priceAnnotation: CPTPlotSpaceAnnotation?复制代码
接下来在barPlot(for:barWasSelectedAtRecord:with:)方法中添加如下代码:
// 1 - 条形图是否隐藏?
if plot.isHidden == true {
return
// 2 - 若未隐藏则创建文本样式
let style = CPTMutableTextStyle()
style.fontSize = 12.0
style.fontName = "HelveticaNeue-Bold"
// 3 - 创建标记
guard let price = number(for: plot,
field: UInt(CPTBarPlotField.barTip.rawValue),
record: idx) as? CGFloat else { return }
priceAnnotation?.annotationHostLayer?.removeAnnotation(priceAnnotation)
priceAnnotation = CPTPlotSpaceAnnotation(plotSpace: plot.plotSpace!, anchorPlotPoint: [0,0])
// 4 - 创建数字格式化器
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
// 5 - 创建标记的文本层
let priceValue = formatter.string(from: NSNumber(cgFloat: price))
let textLayer = CPTTextLayer(text: priceValue, style: style)
priceAnnotation!.contentLayer = textLayer
// 6 - 获取条形图索引
var plotIndex: Int = 0
if plot == plot1 {
plotIndex = 0
else if plot == plot2 {
plotIndex = 1
else if plot == plot3 {
plotIndex = 2
// 7 - 设置标记的锚点
priceAnnotation!.anchorPlotPoint = [NSNumber(cgFloat: x), NSNumber(cgFloat: y)]
// 8 - 添加标记
guard let plotArea = plot.graph?.plotAreaFrame?.plotArea else { return }
plotArea.addAnnotation(priceAnnotation)
}复制代码
分析一下代码:
如果条形图隐藏了则不会添加标记,直接返回,虽然条形图现在不具有隐藏的功能,会在下一部分来实现。
创建标记的文本样式。
得到所指定条形图的数值,并创建一个标记对象。
创建一个数字格式化器,用它来格式化要显示的数据。
创建一个文本层显示格式化后的数据,并将标记的内容层设置为这个文本层。
获取需要显示标记的条形图索引。
根据条形图的索引计算标记的位置,使用计算后的位置设置标记的锚点。
最后将标记添加到graph中。
构建并运行,每当你点击条形图中的粗线条时,就会在它的上面显示出数值。
好极了! :]
条形图看起来不错,不过顶部的开关按钮貌似没什么作用,是时候实现它的功能了。
首先需要添加一个辅助方法,在switch3Changed(_:)后添加这个方法:
func hideAnnotation(graph: CPTGraph) {
guard let plotArea = graph.plotAreaFrame?.plotArea,
let priceAnnotation = priceAnnotation else {
return
plotArea.removeAnnotation(priceAnnotation)
self.priceAnnotation = nil
}复制代码
这段代码的作用是若存在标记则移除它。
下面想让用户使用开关按钮来改变条形图中所显示的货币。
为了实现它,需要使用下面的代码替换switch1Changed(_:), switch2Changed(_:)和switch3Changed(_:)方法:
@IBAction func switch1Changed(_ sender: UISwitch) {
let on = sender.isOn
if !on {
hideAnnotation(graph: plot1.graph!)
plot1.isHidden = !on
@IBAction func switch2Changed(_ sender: UISwitch) {
let on = sender.isOn
if !on {
hideAnnotation(graph: plot2.graph!)
plot2.isHidden = !on
@IBAction func switch3Changed(_ sender: UISwitch) {
let on = sender.isOn
if !on {
hideAnnotation(graph: plot3.graph!)
plot3.isHidden = !on
}复制代码
逻辑非常简单,如果开关按钮是关闭状态则隐藏对应的条形图和标记,若为打开状态则设置条形图为可见的。
构建并运行,可以随意切换显示的条形图,干的不错!