SwiftUI Charts 函数绘图完全指南

SwiftUI Charts 函数绘图完全指南

SwiftUI Charts 框架自 iOS 16 引入以来,已成为在 SwiftUI 应用中创建数据可视化图表的强大工具。随着 iOS 18 的发布,Apple 为其增添了令人兴奋的新功能:函数绘图 (Function Plotting)。这意味着开发者现在可以直接使用 LinePlotAreaPlot 来绘制数学函数,而无需预先计算所有数据点。这为科技、教育、金融等领域的应用开辟了新的可能性。

本文将深入探讨如何在 SwiftUI Charts 中绘制函数,涵盖从基础概念到高级技巧的方方面面。

1. SwiftUI Charts 与函数绘图概述

SwiftUI Charts 是一个声明式的框架,它允许开发者以简洁直观的方式构建各种类型的图表,如折线图、条形图、面积图等。其核心优势在于与 SwiftUI 的无缝集成,支持深度的自定义、动画和交互性。

在 iOS 18 中,LinePlotAreaPlot 新增了直接接受函数作为参数的能力。这意味着你可以传递一个闭包(closure),该闭包接收一个 Double 类型的输入值(如 x),并返回另一个 Double 类型的输出值(如 y = f(x))。图表框架会自动在指定的定义域内计算足够的点来平滑地呈现函数曲线。

1.1 函数绘图的典型应用场景

  • 教育和学习工具:可视化数学函数、物理公式或算法行为。

  • 科学和工程应用:绘制实验数据的拟合曲线、模拟结果或理论模型。

  • 金融分析:展示价格趋势线、收益率曲线或统计分布。

  • 音频和信号处理:显示波形、频谱或滤波器响应。

  • 数据分析和比较:将理论预期函数覆盖在实际测量数据之上进行对比。

2. 开始绘制第一个函数

2.1 基本设置

要使用 SwiftUI Charts,首先确保你的项目满足以下要求:

  • Xcode:使用最新版本的 Xcode(支持 iOS 18 的版本)。

  • 部署目标:将应用的 iOS 部署目标设置为 iOS 18 或更高版本。

  • 导入框架 :在需要使用图表的 SwiftUI 视图中,导入 Charts 框架。

swift 复制代码
import SwiftUI

import Charts

2.2 绘制一个简单的二次函数

让我们从最经典的例子开始:绘制二次函数 ( f(x) = x^2 )。

swift 复制代码
struct QuadraticFunctionPlot: View {

var body: some View {

Chart {

LinePlot(x: "x", y: "x²") { x in

// 这是计算 y = f(x) 的函数闭包

return x * x // 或者使用 pow(x, 2)

}

.foregroundStyle(.blue) // 设置线条颜色

}

// 设置 x 轴和 y 轴的显示范围

.chartXScale(domain: -2.0 ... 2.0)

.chartYScale(domain: 0.0 ... 4.0)

.frame(height: 300)

.padding()

}

}

在这段代码中:

  • LinePlot 初始化器需要几个参数:

  • xy:这些是字符串标识符,用于辅助功能(Accessibility)和图表上下文。

  • 闭包 { x in ... }:这是核心部分。它定义了函数 ( y = f(x) )。对于每个需要绘制的 x 值,图表框架都会调用这个闭包来计算对应的 y 值。

  • chartXScalechartYScale 修饰符用于设置图表的显示范围,这相当于限制了函数的定义域和值域。这对于聚焦于函数的特定区域至关重要。

  • foregroundStyle 修饰符为函数曲线设置颜色。

2.3 绘制正弦函数

三角函数是另一个常见的绘图用例。以下是如何绘制正弦波 ( f(x) = sin(x) ) 的例子:

swift 复制代码
struct SineFunctionPlot: View {

var body: some View {

Chart {

LinePlot(x: "x", y: "sin(x)") { x in

return sin(x)

}

.foregroundStyle(.red)

}

.chartXScale(domain: -3.0 * .pi ... 3.0 * .pi)

.chartYScale(domain: -1.5 ... 1.5)

.frame(height: 300)

.padding()

}

}

3. 使用 AreaPlot 填充函数曲线

AreaPlotLinePlot 类似,但它会填充函数曲线和 x 轴(或其他基线)之间的区域,这对于表示积分、累积值或 simply 突出显示特定区域非常有用。

swift 复制代码
struct QuadraticAreaPlot: View {

var body: some View {

Chart {

AreaPlot(x: "x", y: "x²") { x in

return x * x

}

.foregroundStyle(.orange.gradient) // 使用渐变填充效果更好

}

.chartXScale(domain: -2 ... 2)

.chartYScale(domain: 0 ... 4)

.frame(height: 300)

.padding()

}

}

你可以将 LinePlotAreaPlot 组合在同一个图表中,以同时显示轮廓和填充区域。

swift 复制代码
struct CombinedPlot: View {

var body: some View {

Chart {

// 先绘制面积区域

AreaPlot(x: "x", y: "x²") { x in

pow(x, 2)

}

.foregroundStyle(.orange.opacity(0.3)) // 设置半透明填充

  


// 再在同一区域上绘制线条

LinePlot(x: "x", y: "x²") { x in

pow(x, 2)

}

.foregroundStyle(.orange)

}

.chartXScale(domain: -2 ... 2)

.chartYScale(domain: -4 ... 4)

}

}

4. 处理异常值:NaN 与 Infinity

数学函数在某些点上可能是未定义的(例如,tan(x) 在 π/2 处趋于无穷大)。SwiftUI Charts 要求你在函数闭包中处理这些情况,返回特定的值来告知框架如何处置。

  • 返回 Double.nan:表示该点未定义。图表将在此处断开,不连接左右两侧的线段。

  • 返回 Double.infinity-Double.infinity:表示正无穷或负无穷。图表框架会以某种方式处理这些点(通常会在图表的边界处截断)。

绘制正切函数 ( f(x) = tan(x) ) 是一个很好的例子:

swift 复制代码
struct TangentFunctionPlot: View {

var body: some View {

Chart {

LinePlot(x: "x", y: "tan(x)") { x in

let result = tan(x)

// 检查结果是否为无穷大或无效值,返回 NaN 来中断绘图

if result.isInfinite || result.isNaN {

return Double.nan

}

return result

}

.foregroundStyle(.purple)

}

.chartXScale(domain: -3.0 * .pi ... 3.0 * .pi)

.chartYScale(domain: -5 ... 5) // 限制 y 轴范围,否则无穷大会导致缩放问题

.frame(height: 300)

.padding()

}

}

重要 :处理无穷大时,通常最好也使用 chartYScale 限制 y 轴的范围,以防止图表自动缩放到一个不合理的巨大范围。

5. 参数方程绘图

除了标准的 y = f(x) 函数,SwiftUI Charts 还支持参数方程。在参数方程中,x 和 y 坐标都是另一个变量(通常称为 t)的函数。

例如,绘制一个螺旋线,其参数方程为:

  • ( x(t) = t \cdot cos(t) )

  • ( y(t) = t \cdot sin(t) )

swift 复制代码
struct SpiralParametricPlot: View {

@State private var parameterRange: ClosedRange<Double> = 0 ... 4 * .pi

  


var body: some View {

VStack {

Chart {

LinePlot(x: "x", y: "y", t: "t", domain: parameterRange) { t in

let x = t * cos(t)

let y = t * sin(t)

return (x, y) // 返回一个包含 x 和 y 的元组 (Double, Double)

}

.foregroundStyle(.green)

}

.chartXScale(domain: -50 ... 50)

.chartYScale(domain: -50 ... 50)

.frame(height: 400)

  


// 使用 Slider 动态改变参数 t 的范围

Slider(value: $parameterRange, in: 0...100)

Text("t range: \(parameterRange.lowerBound, format: .number) to \(parameterRange.upperBound, format: .number)")

}

.padding()

}

}

请注意:

  • LinePlot 初始化器使用了 t 参数和 domain 参数来指定参数变量及其取值范围。

  • 闭包现在返回的是一个 (Double, Double) 元组,分别代表 x 和 y 坐标。

  • 这个例子还结合了 @StateSlider,实现了用户交互,动态改变参数范围,从而使图表动起来。

6. 高级技巧与自定义

6.1 叠加函数与数据系列

SwiftUI Charts 的一个强大功能是可以在同一图表中轻松组合不同的标记(marks)。这意味着你可以将函数图覆盖在原始数据之上进行比较。

假设你有一组数据点,并且你绘制了一条最佳拟合线(函数):

swift 复制代码
struct DataPoint: Identifiable {

let id = UUID()

let x: Double

let y: Double

}

  


struct DataWithFitPlot: View {

let sampleData: [DataPoint] = [

DataPoint(x: 1.0, y: 1.2),

DataPoint(x: 2.0, y: 3.9),

DataPoint(x: 3.0, y: 8.1),

DataPoint(x: 4.0, y: 17.5),

// ... 更多数据点

]

  


var body: some View {

Chart {

// 绘制原始数据散点

ForEach(sampleData) { point in

PointMark(

x: .value("X", point.x),

y: .value("Y", point.y)

)

.foregroundStyle(.red)

.symbolSize(100)

}

  


// 覆盖绘制拟合的函数曲线(例如二次拟合)

LinePlot(x: "x", y: "x²") { x in

return x * x // 这是一个简单的 y = x² 模型

}

.foregroundStyle(.blue)

.lineStyle(StrokeStyle(lineWidth: 2, dash: [5]))

}

.chartXScale(domain: 0 ... 5)

.chartYScale(domain: 0 ... 20)

.frame(height: 300)

.padding()

}

}

6.2 自定义样式与动画

你可以使用丰富的修饰符来自定义函数图表的外观:

  • 线条样式 :使用 lineStyle 修饰符设置线宽、虚线模式等。
swift 复制代码
LinePlot(...) { ... }

.foregroundStyle(.blue)

.lineStyle(StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
  • 面积渐变 :对 AreaPlot 使用渐变填充可以创造更美观的视觉效果。
swift 复制代码
AreaPlot(...) { ... }

.foregroundStyle(

LinearGradient(

colors: [.blue, .clear],

startPoint: .top,

endPoint: .bottom

)

)
  • 动画 :当函数参数或定义域发生变化时,SwiftUI Charts 会自动应用平滑的动画过渡。你可以使用 animation 修饰符来控制动画的类型和时长。
swift 复制代码
.animation(.easeInOut(duration: 1.0), value: parameterRange)

6.3 性能考量

虽然函数绘图非常方便,但对于计算量非常大的复杂函数,或者需要极高精度的场合,需要注意性能。图表框架会自动决定需要计算多少个点来渲染曲线。在大多数情况下这是优化的,但如果你遇到性能问题,可以考虑:

  1. 预先计算 :对于极其复杂的函数,如果交互不是必须的,可以考虑预先计算一组数据点,然后使用传统的 LineMarkForEach 来绘制。

  2. 限制定义域 :精确设置 chartXScaledomain,避免计算不必要的区域。

7. 实际应用案例

7.1 在教育类 App 中展示函数性质

你可以创建一个交互式界面,让学生动态改变函数的参数(例如,二次函数 ( ax^2 + bx + c ) 中的 a, b, c),并实时观察图像的变化。这比静态图片更能帮助学生理解参数的影响。

7.2 在科学计算 App 中可视化物理公式

例如,绘制抛体运动的轨迹方程,或者绘制阻尼振荡的位移-时间曲线。函数绘图使得模拟这些物理过程变得非常简单。

7.3 在金融 App 中绘制理论模型

将 Black-Scholes 期权定价模型的理论曲线覆盖在市场的实际期权价格数据上,进行可视化对比和分析。

8. 总结与展望

iOS 18 为 SwiftUI Charts 引入的函数绘图功能,极大地扩展了其应用范围,使其从主要处理离散数据点,延伸到了连续数学函数的领域。LinePlotAreaPlot 与函数闭包的结合,提供了一种非常简洁、强大且声明式的方法来可视化数学概念。

::: tips 核心要点回顾

  • 直接绘图:无需预先计算数据点数组,直接传递函数闭包。

  • 处理异常 :使用 Double.nanDouble.infinity 来正确处理未定义点或无穷大。

  • 参数方程 :支持通过单一参数 t 来定义复杂的曲线路径。

  • 组合叠加 :可以将函数图与传统的基于数据的图表(如 PointMarkBarMark)轻松组合。

  • 交互与动画:通过与 SwiftUI 状态绑定,可以创建动态、交互式的函数可视化效果。

:::

SwiftUI Charts 框架仍在不断发展和增强。可以期待未来版本会带来更多类型的函数绘图支持、更精细的控制选项以及更强大的交互能力。

xuanhu.info/projects/it...

相关推荐
HarderCoder4 小时前
Swift 6.2 新特性 `@concurrent` 完全导读
swift
HarderCoder4 小时前
Swift 里的“橡皮擦”与“标签”——搞懂 existentials 与 primary associated type
swift
权咚18 小时前
阿权的开发经验小集
git·ios·xcode
用户0919 小时前
TipKit与CloudKit同步完全指南
ios·swift
法的空间1 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918411 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
00后程序员张1 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
东坡肘子1 天前
完成 Liquid Glass 的适配了吗?| 肘子的 Swift 周报 #0102
swiftui·swift·apple
Magnetic_h2 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa