案例:实时更新的股票价格折线图
概述
本案例将演示如何使用 SwiftUI Charts 框架构建一个实时更新的股票价格折线图。通过结合 ObservableObject 和 Timer,我们将实现动态数据模拟和可视化,展示 SwiftUI 在金融类应用中的实际应用场景。
实现步骤
1. 数据模型设计
struct StockPrice: Identifiable {
let id = UUID()
let timestamp: Date
let price: Double
}
class StockDataModel: ObservableObject {
@Published var prices: [StockPrice] = []
private var timer: Timer?
init() {
startMockDataStream()
}
private func startMockDataStream() {
timer = Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true
) { [weak self] _ in
self?.generateNewDataPoint()
}
}
private func generateNewDataPoint() {
let lastPrice = prices.last?.price ?? 100.0
let newPrice = lastPrice + Double.random(in: -2.0...2.0)
let newDataPoint = StockPrice(
timestamp: Date(),
price: max(0, newPrice) // 确保价格不为负
prices.append(newDataPoint)
if prices.count > 30 { // 只保留最近30个数据点
prices.removeFirst()
}
}
}
2. 基础折线图实现
struct StockChartView: View {
@StateObject private var model = StockDataModel()
var body: some View {
Chart(model.prices) { data in
LineMark(
x: .value("Time", data.timestamp),
y: .value("Price", data.price)
)
.interpolationMethod(.catmullRom)
PointMark(
x: .value("Time", data.timestamp),
y: .value("Price", data.price)
)
.symbolSize(50)
}
.chartXAxis {
AxisMarks(values: .automatic) { value in
AxisGridLine()
AxisTick()
AxisValueLabel {
if let date = value.as(Date.self) {
Text(date, style: .time)
}
}
}
}
.chartYScale(domain: [
(model.prices.map { $0.price }.min() ?? 0) - 5,
(model.prices.map { $0.price }.max() ?? 0) + 5
])
.padding()
}
}
3. 添加实时交互功能
struct InteractiveStockChart: View {
@StateObject private var model = StockDataModel()
@State private var selectedPrice: StockPrice?
var body: some View {
VStack {
Chart(model.prices) { data in
LineMark(...) // 同基础实现
if let selectedPrice, selectedPrice.id == data.id {
RuleMark(x: .value("Selected", data.timestamp))
.foregroundStyle(.gray.opacity(0.3))
.annotation(position: .top) {
VStack(alignment: .leading) {
Text("Price: \(data.price.formatted(.number.precision(.fractionLength(2))))")
Text(data.timestamp, style: .time)
}
.padding()
.background {
RoundedRectangle(cornerRadius: 4)
.fill(.background)
.shadow(radius: 2)
}
}
}
}
.chartOverlay { proxy in
GeometryReader { geometry in
Rectangle()
.fill(.clear)
.contentShape(Rectangle())
.gesture(
DragGesture()
.onChanged { value in
let xPosition = value.location.x
if let date: Date = proxy.value(atX: xPosition) {
selectedPrice = model.prices
.sorted { abs($0.timestamp.timeIntervalSince(date)) < abs($1.timestamp.timeIntervalSince(date)) }
.first
}
}
)
}
}
HStack {
Button("Pause") { model.pauseUpdates() }
Button("Resume") { model.resumeUpdates() }
}
}
}
}
4. 性能优化技巧
- 数据采样:当数据点超过100个时,采用降采样显示
.samplingStrategy(.count(100))
- 使用
DrawingGroup进行GPU加速:
.drawingGroup()
- 异步渲染:
.task {
await model.fetchInitialData()
}
完整案例展示
![股票折线图示例]
关键知识点
ObservableObject实现数据动态更新LineMark和PointMark的组合使用- 手势交互与
chartOverlay的结合 - 时间序列数据的X轴格式化
- 实时数据流的性能优化
扩展练习
- 添加成交量柱状图(双Y轴)
- 实现K线图(蜡烛图)展示
- 接入真实的股票API(如Alpha Vantage)
