Post

Replies

Boosts

Views

Activity

SwiftUI Charts: poor performance when synchronizing scrolling among multiple charts
I'm currently learning Swift and SwiftUI. I'm working at a MacOS app that will need to visualize a number of traces. The user must be able to scroll along the X axis of any of the traces and the other ones should be scrolling synchronously (think about a visualization similar to Apple's own "Instruments" app). I'm using SwiftUI Charts framework for visualizing the traces, this on MacOS 14.0 (beta at the time of writing) and Xcode 15 beta 6. The app is also built against the latest MacOS target (explicitly to use the new Charts functionalities). My implementation manages to correctly synchronize the scrolling of the separate charts using a binding in combination with the chartScrollPosition method, however the scrolling performance is really terrible (the scrolling movement is sluggish and jittery). If I disable the synchronization by removing the binding and chartScrollPosition calls, the charts can be scrolled independently with no performance issues. I have the feeling that I'm making some mistake in the way I used the binding, which is causing some internal loop in the UI update/drawing process. This is also supported by the logging message I get in the console when I'm scrolling: onChange(of: Optional\<CGRect\>) action tried to update multiple times per frame. I included below the code for a test app that reproduces the issue: ContentView.swift import SwiftUI struct ContentView: View { @State private var markerValue = 0.0 var body: some View { VStack { DataTraceView(markerValue: $markerValue) } .padding() } } DataTraceView.swift import SwiftUI import Charts private let SAMPLES = 10000 func generateRandomDataPoints(count: Int) -> [DataPoint] { return (0..<count).map { idx in DataPoint(id: idx, xValue: Double(idx), yValue: Double.random(in: 0..<1)) } } struct DataPoint: Identifiable { var id: Int var xValue: Double var yValue: Double } struct DataTraceView: View { @State var testVector1: [DataPoint] = generateRandomDataPoints(count: SAMPLES) @State var testVector2: [DataPoint] = generateRandomDataPoints(count: SAMPLES) @Binding var markerValue: Double var body: some View { VStack { Chart(testVector1) { LineMark( x: .value("Time", $0.xValue), y: .value("Speed", $0.yValue) ) } .chartScrollPosition(x: $markerValue) .chartXVisibleDomain(length: 15) .chartScrollableAxes(.horizontal) Chart(testVector2) { LineMark( x: .value("Time", $0.xValue), y: .value("LatG", $0.yValue) ) } .chartScrollPosition(x: $markerValue) .chartXVisibleDomain(length: 15) .chartScrollableAxes(.horizontal) } } } Any ideas about what am I doing wrong?
3
3
1.4k
Aug ’23