SwiftUI Charts添加RuleMark实现十字线时严重卡顿问题求助
问题:SwiftUI Charts中RuleMark十字线导致实时更新图表卡顿
我在SwiftUI Charts里用RuleMark实现了十字线功能,图表每秒更新一次数据,但开启十字线后出现严重卡顿。试过几种临时方案都没解决,注释掉RuleMark后图表就能回到流畅的60FPS。
初始实现代码
struct DataPoint: Identifiable { let x: Date, let y: Int, var id: Date = {x} } struct MyView: View { @State var xPosition : Date? @State var dataPoints: [DataPoint] var body: some View { if let xPosition { Text("xPosition \(xPosition.description)") } Chart { ForEach(datapoints) { point in LineMark(...point...) } if let xPosition { Plot { RuleMark(x: .value("x", xPosition)) .foregroundStyle(.blue) } } } .chartXSelection(value: $xPosition) } }
尝试官方手势方案后的问题
我改用了官方的手势方案,但调用proxy.value方法将手势坐标转换为图表对应值时,耗时高达30-50ms,依然存在性能问题:
chartOverlay { proxy in Rectangle().fill(.clear).contentShape(Rectangle()).gesture( // 省略官方示例中的模板代码 // 将坐标转换为图表的日期和数值,此步骤耗时30-50ms if let (date, value) = proxy.value(at: location, as: (Date, Float64).self) { xPosition = date yPosition = value } else { xPosition = nil yPosition = nil } ) }
最小复现代码
import SwiftUI import Charts import Combine import QuartzCore import Foundation struct ContentView: View { @State var data: DataPump = DataPump() @State var selectedX: Date? = nil @State var selectedY: Int? = nil @State var crossHairs = true var body: some View { FrameRateView() Toggle("显示十字线", isOn: $crossHairs) if crossHairs { chart .chartXSelection(value: $selectedX) .chartYSelection(value: $selectedY) } else { chart } } @ViewBuilder var chart: some View { Chart { ForEach(Array(data.datapoints.enumerated()), id: \.offset) { i, point in LineMark(x: .value("time", point.date), y: .value("amount", point.value)) RectangleMark(xStart: .value("", data.datapoints2[i].date.addingTimeInterval(-0.2)), xEnd: .value("", data.datapoints2[i].date.addingTimeInterval(0.2)), yStart: .value("", data.datapoints2[i].value - 3), yEnd: .value("", data.datapoints2[i].value + 3)).foregroundStyle(.red) } if crossHairs, let selectedX, let selectedY { RuleMark(x: .value("X", selectedX)) RuleMark(y: .value("Y", selectedY)) } } } } @Observable class DataPump { var cancelable : Cancellable? = nil var datapoints: [DataPoint] = { let now = Date() var arr = [DataPoint]() for i in 0...180 { arr.append(DataPoint(date: now.addingTimeInterval(-Double(180 - i)))) } return arr }() var datapoints2: [DataPoint] = { let now = Date() var arr = [DataPoint]() for i in 0...180 { arr.append(DataPoint(date: now.addingTimeInterval(-Double(180 - i)))) } return arr }() init() { cancelable = Timer.publish(every: 1, on: .main, in: .common) .autoconnect() .sink(receiveValue: { value in self.datapoints.append(DataPoint(date: value)) self.datapoints2.append(DataPoint(date: value)) if self.datapoints.count > 180 { self.datapoints.removeFirst(self.datapoints.count - 180) self.datapoints2.removeFirst(self.datapoints2.count - 180) } }) } } struct DataPoint: Identifiable { let date: Date let value: Int = Int.random(in: 7...15) var id: Date { date } } struct FrameRateView: View { @State private var displayLink = DisplayLink() var body: some View { Text(String(format: "帧率: %.0f FPS", displayLink.frameRate)) } } @Observable class DisplayLink { var frameRate: Double = 0 private var displayLink: CADisplayLink? private var lastTimeStamp: CFTimeInterval = 0 private var cancellable: Cancellable? init() { self.displayLink = NSScreen.main?.displayLink(target: self, selector: #selector(updateFrameRate)) self.displayLink?.add(to: .main, forMode: .common) self.lastTimeStamp = self.displayLink!.timestamp } @objc func updateFrameRate() { let currentTimeStamp = displayLink!.timestamp let elapsedTime = currentTimeStamp - lastTimeStamp lastTimeStamp = currentTimeStamp frameRate = 1 / elapsedTime } }
内容的提问来源于stack exchange,提问作者Avba
相关产品推荐
相关产品推荐

