简介

在 WWDC2022 上,苹果为 SwiftUI 提供了图表相关的包Charts,这下就不用自己费劲实现图表功能了。代码量一下子从四五百行变成了十几行,甚至几行(还记得当时为了写图表功能写的快吐血了)。所以就想好好研究一下自带的图表功能,这样方便以后的开发和使用。

Charts支持 iOS/iPadOS 16.0、macOS 13.0、Mac Catalyst 16.0、tvOS 16.0 和 watchOS 9.0 及更新的系统上使用,如果是老系统的话还是得手写。

Charts支持六种样式的图表,但是通过组合可以实现多种图表,下面是发布会演示的图集:

swift 图片处理 swiftui 图表_swiftui

除了这些,还有更多的样式可以实现,所以Charts真的需要好好研究一下。

但是本文主要介绍三种:条状图(BarMark)、点图(PointMark)和线图(LineMark)。下面会先介绍图表的基础使用方法,然后依次并详细介绍每种样式的使用方式(每种样式的使用方法大致相同,但是各有各的特色,比如线图可以一次显示多条线,条状图可以在一个图表中自定义显示多种颜色)。

swift 图片处理 swiftui 图表_数据_02

基础使用方法

在开始制作图表之前,首先要准备好数据。假设我们要统计物体的形状。

数据如下:

样式

数量

立方体

5

球体

4

锥体

4

这里的数据结构是一对一的,一个类型对应一个数量,可以说是“一维”的。

定义数据结构和数据

以下数据结构和数据适用于三种样式的图表,属于基础款。

为数据结构定义的结构体如下:

struct ToyShape: Identifiable {
    var type: String
    var count: Double
    var id = UUID()
}

初始化的存放数据的数组如下:

var data: [ToyShape] = [
    .init(type: "立方体", count: 5),
    .init(type: "球体", count: 4),
    .init(type: "锥体", count: 4)
]

如何创建一个简单的图表

三种样式的基础图表使用方法几乎一样,所以这里只用讲一种即可。

首先需要导入Charts包,如下:

import Charts

然后使用方法很简单,首先创建一个Chart界面,作为展示数据的容器:

Chart {
	//添加数据
}

接下来就是将数据变成点/线/条,对应的是PointMark()/LineMark()/BarMark()
下面使用条状图进行演示,如果想换样式,只需要改成对应的名称,并且为了少写代码,或者方便更改,这里建议使用ForEach(后面会介绍如果不用ForEach,直接使用Chart也可以实现同样的效果),除了使代码简洁之外,还可以通过一些手段来进行排序(利用ForEachid属性):

Chart {
    ForEach(data) { shape in
        BarMark(
        	//这里的x表示图表的x轴,这里的y表示图表的y轴
        	//需要注意的是这里的数据类型是PlottableValue
            x: .value("形状", shape.type),
            y: .value("总数", shape.count)
        )
    }
}

这时候显示效果如下:

swift 图片处理 swiftui 图表_ios_03

可以看到效果不错。而且这样的话,如果想换成其他样式只要把代码中的BarMark更改即可,不用依次去改。

或者可以直接在Chart后面加个括号来实现同样的效果,如下:

Chart(data) { shape in
    BarMark(
        //这里的x表示图表的x轴,这里的y表示图表的y轴
        x: .value("形状", shape.type),
        y: .value("总数", shape.count)
    )
}

如下改成线图:

Chart {
    ForEach(data) { shape in
        LineMark(
            x: .value("形状", shape.type),
            y: .value("总数", shape.count)
        )
    }
}

swift 图片处理 swiftui 图表_swift 图片处理_04


点图:

Chart {
    ForEach(data) { shape in
        PointMark(
            x: .value("形状", shape.type),
            y: .value("总数", shape.count)
        )
    }
}

swift 图片处理 swiftui 图表_swift 图片处理_05

定义“二维”数据结构和初始化存放数据的数组

上一节只根据物体的形状进行了统计,那如果在加上颜色呢?

假设数据如下:

数量

粉色

黄色

紫色

绿色

总数

立方体

1

1

1

2

5

球体

2

1

1

0

4

锥体

0

2

1

1

4

总数

3

4

3

3

13

这样多数据结构可以说是一对二了,颜色和形状对应一个数量,可以称之为“二维”数据,比之前的“一维”难了一些。

那么就需要更改数据结构和存放数据的数组了,如下:

//数据结构需要多一个颜色
struct ToyShape: Identifiable {
    var color: String
    var type: String
    var count: Double
    var id = UUID()
}

//初始化存放数据的数组
var stackedBarData: [ToyShape] = [
    .init(color: "绿色", type: "立方体", count: 2),
    .init(color: "绿色", type: "球体", count: 0),
    .init(color: "绿色", type: "锥体", count: 1),
    .init(color: "紫色", type: "立方体", count: 1),
    .init(color: "紫色", type: "球体", count: 1),
    .init(color: "紫色", type: "锥体", count: 1),
    .init(color: "粉色", type: "立方体", count: 1),
    .init(color: "粉色", type: "球体", count: 2),
    .init(color: "粉色", type: "锥体", count: 0),
    .init(color: "黄色", type: "立方体", count: 1),
    .init(color: "黄色", type: "球体", count: 1),
    .init(color: "黄色", type: "锥体", count: 2)
]

下面继续先以条状图为例,这里倒不是为了省事,而是为了方便理解,后面会依次解释说明三种样式。

如何创建一个可以显示“二维”数据的图表

对于这种“二维”数据,需要在BarMark()后面加上一个foregroundStyle(by:)方法才可以显示,并且在图表左下方,还会显示一个图例。

那么来写一个图表,用于显示不同形状中,不同颜色的占比。代码如下:

Chart {
	ForEach(stackedBarData) { shape in
		BarMark(
			//这样写就是显示不同形状中,不同颜色的占比
			x: .value("形状", shape.type),
			y: .value("总数", shape.count)
		)
		//这里由于颜色都可以和系统自带的对应不上,后面会介绍如何自定义颜色来进行正确的显示
		.foregroundStyle(by: .value("颜色", shape.color))
	}
}

显示效果如下:

swift 图片处理 swiftui 图表_前端_06

这里就可以很直观的看到不同形状中,不同颜色的占比了。但是如果你仔细看会发现这里颜色对应的不对

那么线图和点图是如何显示的呢?

线图看起来会很复杂,如下:

swift 图片处理 swiftui 图表_数据_07


每一根线表示一种颜色,每个 X 轴区域对应一个形状。点图和线图阅读方式一样,不同颜色点表示不同的颜色,每个 X 轴区域对应一个形状,但是看起来就简单不少,如下:

swift 图片处理 swiftui 图表_前端_08

自定义图表的颜色

上一节的图表中,由于颜色都可以和系统自带的对应不上,就需要开发者手动自定义一下来显示正确的颜色。
开发者需要在Chart后面使用chartForegroundStyleScale()关键字让绿色紫色粉色黄色字符串和颜色(系统自带的或者自定义的颜色)进行关联,如下:

Chart {
	ForEach(stackedBarData) { shape in
		BarMark(
			x: .value("形状", shape.type),
			y: .value("总数", shape.count)
		)
		.foregroundStyle(by: .value("颜色", shape.color))
	}
}
//这里将字符串和系统自带的颜色进行对应,以便显示正确的颜色
.chartForegroundStyleScale(["绿色": .green, "紫色": .purple, "粉色": .pink, "黄色": .yellow])

现在显示效果如下:

swift 图片处理 swiftui 图表_swift 图片处理_09


可以看到颜色显示正确了。

进阶使用方法

下面就来讲述一些刚才没有讲到的使用方法。

添加对比线(Rule Mark)

我不太清楚这个东西的中文名,但是一般用于对比,有时候也会用于做图,所以将其称为对比线。

有时候能看到在图表中有一根水平线,来表示超过某一数据,比如下图中的蓝色水平线,可以用来对比数据:

swift 图片处理 swiftui 图表_前端_10


方法很简单,就是在Chart的域中(就是大括号里)加上一个RuleMark,如下:

RuleMark(y: .value("对比线", 3))
	.foregroundStyle(.blue)

这里使用的是纵坐标y,那么可不可以使用横坐标x呢?答案是可以的,但是一般不使用于柱状图,而是用于线图和点图较多。因为柱状图没有使用的必要,而点图和线图中,可以用于定位点或者划分区域。

当然这个RuleMark也可以控制其起点和终点,这里继续以上图为例:

RuleMark(xStart: .value("起点", "立方体"), xEnd: .value("终点", "球体"), y: .value("值", 3))
	.foregroundStyle(.blue)

增加起点和终点之后,效果如下:

swift 图片处理 swiftui 图表_数据_11

当然RuleMark也可以直接用于作图,如下为一个统计每个月种什么的图表:

swift 图片处理 swiftui 图表_前端_12

给线图带上点

刚才演示的线图都是一条条折线,但是如果我们想加上点来让数据更容易理解呢?
开头提到各种样式是可以叠加的,所以可以直接加上点图来实现。

import SwiftUI
import Charts

struct ToyShape: Identifiable {
    var type: String
    var count: Double
    var id = UUID()
}

var data: [ToyShape] = [
    .init(type: "Cube", count: 5),
    .init(type: "Sphere", count: 4),
    .init(type: "Pyramid", count: 4)
]

struct ContentView: View {
    var body: some View {
        VStack {
            Chart(data) { data in
                LineMark(
                    x: .value("Shape Type", data.type),
                    y: .value("Total Count", data.count)
                )
                
                //加上点图来组合形成
                PointMark(
                    x: .value("Shape Type", data.type),
                    y: .value("Total Count", data.count)
                )
            }
        }
        .padding()
    }
}

swift 图片处理 swiftui 图表_swiftui_13

自定义图表颜色

这个和其他组件一样,直接使用ChartforegroundStyle属性即可,如下:

import SwiftUI
import Charts

struct ToyShape: Identifiable {
    var type: String
    var count: Double
    var id = UUID()
}

var data: [ToyShape] = [
    .init(type: "Cube", count: 5),
    .init(type: "Sphere", count: 4),
    .init(type: "Pyramid", count: 4)
]

struct ContentView: View {
    var body: some View {
        VStack {
            Chart(data) { data in
                LineMark(
                    x: .value("Shape Type", data.type),
                    y: .value("Total Count", data.count)
                )
                
                PointMark(
                    x: .value("Shape Type", data.type),
                    y: .value("Total Count", data.count)
                )
            }
            //这里就可以更改颜色
            .foregroundStyle(Color.pink)
        }
        .padding()
    }
}

如果想修改其中的点图或者线图的颜色,在对应的LineMarkPointMark后面使用foregroundStyle属性即可。

swift 图片处理 swiftui 图表_ios_14

提醒

由于图表有很多东西可以挖,所以一篇博客肯定写不完,后续还会继续写相关的博客,如果写了会在这里贴出链接。

如果你看完本文还有一些问题没有解决,那么请查阅官方文档《Swift Charts》,因为目前在 macOS 上,Charts还是 Beta 版本,所以本文的一些内容可能后面会被修改,如果您发现有不能用的地方,还请评论告知一下我哦。

希望能帮到有需要的人~