说实话,在 Swift Charts 出现之前,iOS 上做数据可视化真的挺折腾的。要么用第三方库(比如 Charts/Daniel Gindi),要么自己用 Core Graphics 从零开始画。前者跟 SwiftUI 的集成总是差点意思,后者嘛……你得写一大堆底层代码,光想想就头疼。
转折点出现在 2022 年的 WWDC。Apple 正式推出了 Swift Charts——一个原生的、声明式的图表框架,跟 SwiftUI 深度集成。从 iOS 16 开始可用,它让你几行代码就能搞出好看、可访问、性能又好的图表。
而且 Apple 一直在加码。iOS 17 加了扇形图(SectorMark)和可滚动图表,到了 2025 年 WWDC 发布的 iOS 26,更是直接上了 3D 图表,把数据可视化拉到了一个全新的维度。说真的,第一次看到 Chart3D 的 demo 时,我还是挺震撼的。
这篇文章会从 Swift Charts 的基础概念开始,一步步深入到各种图表类型、交互、滚动、坐标轴定制,最后到 iOS 26 全新的 3D 可视化。不管你是刚入门的新手还是想了解 3D 特性的老手,应该都能找到有用的东西。
开始使用 Swift Charts
好消息是,Swift Charts 内置在 iOS 16+ 的 SDK 里,不用装任何第三方依赖。导入框架就能开始用:
import SwiftUI
import Charts
核心概念其实很简单——用一个 Chart 容器视图,往里面放标记(Mark)就行。每种标记代表一种数据的可视化方式。框架提供了 BarMark、LineMark、AreaMark、PointMark、RuleMark、SectorMark 这些标记类型,基本覆盖了常见需求。
在画图表之前,先定义好数据模型。Swift Charts 跟 Identifiable 协议配合得很好:
struct SalesData: Identifiable {
let id = UUID()
let month: String
let revenue: Double
let category: String
}
let sampleData: [SalesData] = [
SalesData(month: "1月", revenue: 5000, category: "电子产品"),
SalesData(month: "2月", revenue: 8000, category: "电子产品"),
SalesData(month: "3月", revenue: 7500, category: "电子产品"),
SalesData(month: "1月", revenue: 3000, category: "服装"),
SalesData(month: "2月", revenue: 4500, category: "服装"),
SalesData(month: "3月", revenue: 6000, category: "服装"),
]
有了数据模型,就可以开始画各种图表了。
BarMark — 柱状图
柱状图大概是最常见的图表类型了,非常适合做分类数据的比较。在 Swift Charts 里用 BarMark 就能搞定。
基础柱状图
struct BasicBarChart: View {
let data: [SalesData]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
}
.frame(height: 300)
}
}
就这么几行,一个基础柱状图就出来了。
分组柱状图
想按类别分组显示?加上 foregroundStyle(by:) 自动着色,再配合 position(by:) 让柱子并排排列(而不是堆叠):
struct GroupedBarChart: View {
let data: [SalesData]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
.foregroundStyle(by: .value("类别", item.category))
.position(by: .value("类别", item.category))
}
.frame(height: 300)
}
}
堆叠柱状图与样式定制
去掉 position(by:) 的话,同组数据会自动堆叠。你还可以用 clipShape 或 cornerRadius 让柱子更好看:
struct StyledBarChart: View {
let data: [SalesData]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue),
width: .ratio(0.6)
)
.foregroundStyle(by: .value("类别", item.category))
.clipShape(RoundedRectangle(cornerRadius: 6))
}
.chartForegroundStyleScale([
"电子产品": Color.blue,
"服装": Color.orange
])
.frame(height: 300)
}
}
BarMark 的 width 参数支持 .ratio()、.fixed() 和 .automatic 这几种选项,可以精确控制柱子宽度。chartForegroundStyleScale 则用来自定义类别的颜色映射——这个在实际项目中特别有用。
LineMark — 折线图
折线图最适合展示数据随时间的变化趋势了。LineMark 就是干这个的。
基础折线图与多条折线
struct TemperatureData: Identifiable {
let id = UUID()
let date: Date
let temperature: Double
let city: String
}
struct MultiLineChart: View {
let data: [TemperatureData]
var body: some View {
Chart(data) { item in
LineMark(
x: .value("日期", item.date),
y: .value("温度", item.temperature)
)
.foregroundStyle(by: .value("城市", item.city))
.symbol(by: .value("城市", item.city))
}
.frame(height: 300)
}
}
插值方法与线条样式
Swift Charts 提供了好几种插值方法来控制折线的形态。.monotone 给你平滑但保持单调性的曲线,.cardinal 更圆润一些,.catmullRom 则生成经过所有数据点的平滑样条曲线。线条宽度和虚线样式也都能自定义:
struct StyledLineChart: View {
let data: [TemperatureData]
var body: some View {
Chart(data) { item in
LineMark(
x: .value("日期", item.date),
y: .value("温度", item.temperature)
)
.interpolationMethod(.catmullRom)
.lineStyle(StrokeStyle(
lineWidth: 2.5,
dash: [8, 4]
))
.foregroundStyle(.blue.gradient)
}
.frame(height: 300)
}
}
另外,.symbol() 修饰符可以在数据点上加标记符号,内置了 .circle、.square、.triangle、.diamond、.cross 这些形状。
AreaMark — 面积图
面积图本质上就是填充版的折线图,能更直观地感受数据的体量。AreaMark 还支持渐变填充和范围展示,效果还挺漂亮的。
基础面积图与渐变填充
struct GradientAreaChart: View {
let data: [SalesData]
var body: some View {
Chart(data) { item in
AreaMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
.foregroundStyle(
.linearGradient(
colors: [.blue.opacity(0.6), .blue.opacity(0.1)],
startPoint: .top,
endPoint: .bottom
)
)
.interpolationMethod(.catmullRom)
}
.frame(height: 300)
}
}
范围面积图
用 yStart 和 yEnd 参数可以画范围面积图,特别适合展示数据的上下界或者置信区间:
struct RangeData: Identifiable {
let id = UUID()
let month: String
let minTemp: Double
let maxTemp: Double
}
struct RangeAreaChart: View {
let data: [RangeData]
var body: some View {
Chart(data) { item in
AreaMark(
x: .value("月份", item.month),
yStart: .value("最低温", item.minTemp),
yEnd: .value("最高温", item.maxTemp)
)
.foregroundStyle(.green.opacity(0.3))
.interpolationMethod(.monotone)
}
.frame(height: 300)
}
}
PointMark — 散点图
散点图用来展示两个变量之间的关系。PointMark 可以单独用,也可以跟 LineMark 组合——后者在实际项目中其实更常见。
struct StudentScore: Identifiable {
let id = UUID()
let studyHours: Double
let examScore: Double
let subject: String
}
struct ScatterPlotView: View {
let scores: [StudentScore]
var body: some View {
Chart(scores) { item in
PointMark(
x: .value("学习时长", item.studyHours),
y: .value("考试成绩", item.examScore)
)
.foregroundStyle(by: .value("科目", item.subject))
.symbolSize(80)
.symbol(by: .value("科目", item.subject))
}
.chartXAxisLabel("每日学习时长(小时)")
.chartYAxisLabel("考试成绩(分)")
.frame(height: 350)
}
}
把 PointMark 跟 LineMark 搭配使用,可以在折线上清楚地标出每个数据点:
Chart(data) { item in
LineMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
PointMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
.symbolSize(60)
}
RuleMark — 参考线
RuleMark 用来在图表上画参考线——目标值、平均值、阈值这些。别小看这个功能,它能让用户一眼就看出数据有没有达标。
struct ChartWithThreshold: View {
let data: [SalesData]
let target: Double = 6000
var body: some View {
Chart {
ForEach(data) { item in
BarMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
.foregroundStyle(.blue.opacity(0.8))
}
RuleMark(y: .value("目标", target))
.foregroundStyle(.red)
.lineStyle(StrokeStyle(lineWidth: 2, dash: [6, 3]))
.annotation(position: .top, alignment: .leading) {
Text("目标: ¥\(Int(target))")
.font(.caption)
.foregroundStyle(.red)
}
}
.frame(height: 300)
}
}
RuleMark 也能画垂直参考线(用 x 参数就行)。.annotation() 修饰符可以在参考线旁边加文字标注,position 参数控制标注在哪个方向。
SectorMark — 扇形图(iOS 17+)
iOS 17 终于带来了原生的饼图和环形图支持!说「终于」是因为这真的是很多开发者等了好久的功能。SectorMark 用起来也很直观。
饼图与环形图
struct ExpenseCategory: Identifiable {
let id = UUID()
let name: String
let amount: Double
}
struct DonutChartView: View {
let expenses: [ExpenseCategory] = [
ExpenseCategory(name: "餐饮", amount: 3200),
ExpenseCategory(name: "交通", amount: 1500),
ExpenseCategory(name: "娱乐", amount: 2100),
ExpenseCategory(name: "购物", amount: 4800),
ExpenseCategory(name: "其他", amount: 1200),
]
var body: some View {
Chart(expenses) { item in
SectorMark(
angle: .value("金额", item.amount),
innerRadius: .ratio(0.6),
angularInset: 2
)
.cornerRadius(5)
.foregroundStyle(by: .value("类别", item.name))
}
.frame(height: 350)
}
}
几个关键参数值得说一下:innerRadius 设成 .ratio(0.6) 就变成环形图了(内径是外径的 60%),设成零就是标准饼图;angularInset 在扇区间加间距,视觉上更清爽;cornerRadius 给扇区加圆角,看起来更精致。
交互式扇形图
通过 chartAngleSelection,用户可以点击或拖动来选择扇区:
struct InteractiveDonutChart: View {
let expenses: [ExpenseCategory]
@State private var selectedAngle: Double?
var selectedItem: ExpenseCategory? {
guard let selectedAngle else { return nil }
var cumulative: Double = 0
for expense in expenses {
cumulative += expense.amount
if selectedAngle <= cumulative {
return expense
}
}
return nil
}
var body: some View {
VStack {
Chart(expenses) { item in
SectorMark(
angle: .value("金额", item.amount),
innerRadius: .ratio(0.6),
angularInset: 2
)
.cornerRadius(5)
.foregroundStyle(by: .value("类别", item.name))
.opacity(selectedItem?.name == item.name ? 1.0 : 0.6)
}
.chartAngleSelection(value: $selectedAngle)
.frame(height: 300)
if let selected = selectedItem {
Text("\(selected.name): ¥\(Int(selected.amount))")
.font(.headline)
}
}
}
}
图表交互
Swift Charts 的交互功能做得相当不错,图表不只是静态展示,还能让用户主动探索数据。
选择与悬停
chartXSelection 和 chartYSelection 让用户在图表上做选择操作。配合 chartOverlay,你可以实现类似 tooltip 的效果:
struct InteractiveLineChart: View {
let data: [SalesData]
@State private var selectedMonth: String?
var body: some View {
Chart(data) { item in
LineMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
.interpolationMethod(.catmullRom)
if let selectedMonth, item.month == selectedMonth {
RuleMark(x: .value("选中", selectedMonth))
.foregroundStyle(.gray.opacity(0.3))
.lineStyle(StrokeStyle(lineWidth: 1))
.annotation(
position: .top,
overflowResolution: .init(
x: .fit(to: .chart),
y: .disabled
)
) {
VStack(alignment: .leading, spacing: 4) {
Text(item.month)
.font(.caption)
.foregroundStyle(.secondary)
Text("¥\(Int(item.revenue))")
.font(.headline)
}
.padding(8)
.background(.ultraThinMaterial)
.cornerRadius(8)
}
}
}
.chartXSelection(value: $selectedMonth)
.frame(height: 300)
}
}
如果 X 轴用的是 Date 类型,绑定值类型就改成 Date?。框架会自动帮你匹配最近的数据点,交互动画也是内置的,不用额外配置。体验非常丝滑。
可滚动图表
数据点多的时候,挤在一个画面里看着实在太拥挤。iOS 17 引入了可滚动图表,用户可以左右滑动来浏览更长时间跨度的数据。这个功能在健康、运动、金融类 App 里特别实用。
struct DailyStep: Identifiable {
let id = UUID()
let date: Date
let steps: Int
}
struct ScrollableChartView: View {
let dailySteps: [DailyStep]
@State private var scrollPosition: Date
init(dailySteps: [DailyStep]) {
self.dailySteps = dailySteps
_scrollPosition = State(
initialValue: dailySteps.last?.date ?? .now
)
}
var body: some View {
Chart(dailySteps) { item in
BarMark(
x: .value("日期", item.date, unit: .day),
y: .value("步数", item.steps)
)
.foregroundStyle(.green.gradient)
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 7)
.chartScrollPosition(x: $scrollPosition)
.chartScrollTargetBehavior(
.valueAligned(
matching: DateComponents(weekday: 2),
majorAlignment: .matching(
DateComponents(day: 1)
)
)
)
.frame(height: 300)
}
}
简单解释一下关键部分:chartScrollableAxes(.horizontal) 启用水平滚动;chartXVisibleDomain(length:) 设置可见窗口大小,这里是 7 天;chartScrollPosition 双向绑定滚动位置;chartScrollTargetBehavior 控制滚动对齐行为,让滚动停下来时自动对齐到周一或月初。
坐标轴与图例定制
Swift Charts 的坐标轴和图例都可以高度自定义。如果你对默认样式不满意(虽然默认的已经挺好了),可以这样来调整:
struct CustomizedAxisChart: View {
let data: [SalesData]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("月份", item.month),
y: .value("收入", item.revenue)
)
.foregroundStyle(by: .value("类别", item.category))
}
.chartXAxis {
AxisMarks(values: .automatic) { value in
AxisGridLine()
.foregroundStyle(.gray.opacity(0.3))
AxisTick()
.foregroundStyle(.gray)
AxisValueLabel()
.font(.caption)
.foregroundStyle(.primary)
}
}
.chartYAxis {
AxisMarks(position: .leading) { value in
AxisGridLine(
stroke: StrokeStyle(dash: [4, 4])
)
AxisValueLabel {
if let intValue = value.as(Int.self) {
Text("¥\(intValue)")
.font(.caption2)
}
}
}
}
.chartLegend(position: .bottom, spacing: 20)
.frame(height: 300)
}
}
chartXAxis 和 chartYAxis 闭包里,你可以完全掌控坐标轴的外观。AxisMarks 下面能自定义刻度线、网格线和标签,每个组件都支持独立设置样式。
chartLegend 控制图例的位置。可以放在 .top、.bottom、.leading 或 .trailing,也可以用 .hidden 完全隐藏图例(然后自己做一个自定义版本)。
Chart3D — iOS 26 全新 3D 图表
接下来是最让人兴奋的部分了。
iOS 26(WWDC 2025)给 Swift Charts 带来了最大的一次更新——三维图表。全新的 Chart3D 容器让你能在三维空间里可视化数据,支持 iPhone、iPad、Mac 和 Apple Vision Pro。
这可不只是个炫酷的噱头。对于科学计算、工程分析、金融建模这类需要展示多维数据关系的场景,3D 图表有着很高的实用价值。
基础 3D 散点图
先从一个三维散点图开始吧。Chart3D 里的 PointMark 现在支持 z 轴参数了,可以把数据点投射到三维空间:
struct Particle: Identifiable {
let id = UUID()
let x: Double
let y: Double
let z: Double
let energy: Double
let type: String
}
struct ScatterPlot3DView: View {
let particles: [Particle] = {
var result: [Particle] = []
for _ in 0..<200 {
result.append(Particle(
x: Double.random(in: -10...10),
y: Double.random(in: -10...10),
z: Double.random(in: -10...10),
energy: Double.random(in: 0...100),
type: ["Alpha", "Beta", "Gamma"].randomElement()!
))
}
return result
}()
var body: some View {
Chart3D(particles) { particle in
PointMark(
x: .value("X 坐标", particle.x),
y: .value("能量", particle.energy),
z: .value("Z 坐标", particle.z)
)
.foregroundStyle(by: .value("类型", particle.type))
.symbolSize(particle.energy * 2)
}
.chartXAxisLabel("X 轴")
.chartYAxisLabel("能量 (eV)")
.chartZAxisLabel("Z 轴")
.frame(height: 450)
}
}
这段代码创建了一个粒子分布的 3D 散点图。每个粒子在三维空间中定位,大小由能量值决定,颜色代表粒子类型。chartZAxisLabel 是 iOS 26 新增的修饰符。
SurfacePlot — 三维曲面图
SurfacePlot 是 iOS 26 引入的全新标记类型,用来渲染数学曲面。如果你需要可视化函数关系、地形数据、或者任何能用 f(x, z) 表达的东西,这个就是你要的。
struct MathSurfaceView: View {
var body: some View {
Chart3D {
SurfacePlot(
x: "x",
y: "y",
z: "z"
) { x, z in
sin(x) * cos(z)
}
.xDomain(-Double.pi ... Double.pi)
.zDomain(-Double.pi ... Double.pi)
.foregroundStyle(
.linearGradient(
colors: [.blue, .cyan, .green, .yellow, .red],
startPoint: .bottom,
endPoint: .top
)
)
}
.chartYAxisLabel("y = sin(x) · cos(z)")
.chartXAxisLabel("x")
.chartZAxisLabel("z")
.chart3DProjection(.perspective)
.frame(height: 500)
}
}
这个例子渲染了经典的 sin(x) * cos(z) 曲面。闭包接收 x 和 z 坐标,返回 y 值。xDomain 和 zDomain 定义定义域范围。那个彩虹渐变色映射让高低起伏一目了然,效果真的很赞。
相机投影与视角控制
3D 图表的一大看点就是相机控制。iOS 26 提供了两种投影方式和灵活的视角调整:
struct Interactive3DChartView: View {
@State private var pose = Chart3DPose(
azimuth: .degrees(45),
inclination: .degrees(30)
)
var body: some View {
Chart3D(particles) { particle in
PointMark(
x: .value("X", particle.x),
y: .value("Y", particle.y),
z: .value("Z", particle.z)
)
.foregroundStyle(by: .value("类型", particle.type))
}
.chart3DProjection(.perspective)
.chart3DPose($pose)
.frame(height: 500)
}
}
两种投影模式各有用途:.perspective(透视投影)模拟人眼效果,远的物体看起来更小,有空间深度感;.orthographic(正交投影)没有近大远小,所有物体保持相同比例,更适合精确的科学分析。
Chart3DPose 控制观察视角——azimuth 是水平旋转角度,inclination 是垂直倾斜角度。作为 Binding 传入后,用户可以通过拖动手势旋转 3D 图表。在 Apple Vision Pro 上就更自然了,直接用手势旋转和缩放。
说到 Vision Pro,Chart3D 还支持体积窗口(volumetric window)渲染,3D 图表可以真正悬浮在用户面前的空间中。光想想就觉得酷。
最佳实践与性能优化
聊完了各种图表类型和花哨的 3D 功能,来说说实际项目中需要注意的事情。
数据优化
- 控制数据点数量:折线图和面积图如果数据点超过几千个,应该考虑降采样。LTTB(Largest Triangle Three Buckets)算法是个不错的选择,能在保持数据特征的同时减少点数。
- 用值类型:尽量用 struct 而不是 class 来定义数据模型,这对 SwiftUI 的 diff 机制更友好。
- 别在 body 里做计算:数据的筛选、排序、聚合这些操作要放在 body 外面,用
@State、@StateObject或计算好的属性来存储。这个坑我见过不少人踩。
可访问性
- Swift Charts 内置了 VoiceOver 支持,会自动生成无障碍描述。可以通过
.accessibilityLabel()和.accessibilityValue()自定义描述。 - Audio Graphs 功能让视力障碍用户可以通过声音来「听到」数据趋势变化——这是内置功能,不用额外写代码。
- 选颜色时记得考虑色盲用户。
chartForegroundStyleScale配合系统无障碍色板是个好做法。
设计与色彩
- 遵循平台规范:Swift Charts 默认用系统色彩,自动适应浅色和深色模式。没有特殊需求的话,默认配色其实就挺好。
- 保持简洁:一个图表里别放太多数据系列。3-5 个是比较合理的上限,再多就考虑用筛选器或者分页吧。
- 动画要克制:
.animation()可以给数据变化加过渡动画,但别让动画喧宾夺主,影响用户理解数据。
性能建议
- 数据点很多的图表,用
chartPlotStyle设置固定尺寸,避免不必要的布局重算。 - 滚动图表中,框架会自动管理屏幕外数据的渲染,你不用手动做虚拟化。
- 如果图表需要频繁更新(比如实时数据),考虑用
TimelineView配合固定的刷新间隔来控制更新频率。 - Chart3D 的 3D 渲染比 2D 消耗更多资源。在老设备上记得测试,必要时减少数据点或降低曲面分辨率。
总结
Swift Charts 从 iOS 16 首次亮相到 iOS 26 的 3D 图表,走过了一段很漂亮的进化之路:
- iOS 16(2022):框架首发,带来了 BarMark、LineMark、AreaMark、PointMark、RuleMark、RectangleMark,奠定了声明式图表的基础。
- iOS 17(2023):新增 SectorMark、可滚动图表、交互增强,大幅扩展了能力范围。
- iOS 18(2024):细节优化和性能提升,让框架更加稳定可靠。
- iOS 26(2025):Chart3D 和 SurfacePlot 横空出世,把数据可视化从 2D 带到了 3D,还为 Vision Pro 提供了原生支持。
Swift Charts 的设计理念很好地体现了 Apple 的产品哲学:简单的事情应该简单,复杂的事情应该可能。几行代码就能出一个好看的图表,同时通过丰富的修饰符和选项,你也能做出高度定制化的可视化作品。
展望未来,随着 Vision Pro 生态的成熟,3D 图表的交互方式会变得更丰富。ML 与可视化的结合也值得期待——比如图表自动识别异常值并高亮,或者根据数据特征推荐最佳图表类型。
不管你在做金融 App、健康追踪、数据仪表盘还是科学计算工具,Swift Charts 都能用最少的代码量、最大的表达力来讲好数据的故事。从基础柱状图到 3D 曲面可视化,工具箱已经备好了——现在就开始用吧。