Swift Charts

RSS for tag

Visualize data with highly customizable charts across all Apple platforms using the compositional syntax of SwifUI.

Posts under Swift Charts tag

40 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Swift Charts: chartScrollTargetBehavior fails to snap to a day unit
I am working on a scrollable chart that displays days on the horizontal axis. As the user scrolls, I always want them to be able to snap to a specific day. I implemented the following steps described in this WWDC23 session to achieve this. I have set the chartScrollTargetBehavior to .valueAligned(matching: DateComponents(hour: 0)) I have set the x value unit on the BarMark to Calendar.Component.day I ended up with the chart code that looks like this: Chart(dates, id: \.self) { date in BarMark( x: .value("Date", date, unit: Calendar.Component.day), y: .value("Number", 1) ) .annotation { Text(date.formatted(.dateTime.day())) .font(.caption2) } } .chartXAxis { AxisMarks(format: .dateTime.day()) } .chartScrollableAxes(.horizontal) .chartScrollTargetBehavior(.valueAligned(matching: DateComponents(hour: 0))) .chartXVisibleDomain(length: fifteenDays) .chartScrollPosition(x: $selection) However, this fails to work reliably. There is often a situation where the chart scroll position lands on, for instance, Oct 20, 11:56 PM, but the chart snaps to Oct 21. I attempted to solve this problem by introducing an intermediate binding between a state value and a chart selection. This binding aims to normalize the selection always to be the first moment of any given date. But this hasn't been successful. private var selectionBinding: Binding<Date> { Binding { Calendar.current.startOfDay(for: selection) } set: { newValue in self.selection = Calendar.current.startOfDay(for: newValue) } } It's also worth mentioning that this issue also exists in Apple's sample project on Swift Charts. How would you approach solving this? How can I find a way to make the chart scroll position blind to time values and only recognize whole days? Here's the minimal reproducible example project for your reference.
1
1
550
Nov ’24
SwiftUI Gestures: Sequenced Long Press and Drag
In creating a sequenced gesture combining a LongPressGesture and a DragGesture, I found that the combined gesture exhibits two problems: The @GestureState does not properly update as the gesture progresses through its phases. Specifically, the updating(_:body:) closure (documented here) is only ever executed during the drag interaction. Long presses and drag-releases do not call the updating(_:body:) closure. Upon completing the long press gesture and activating the drag gesture, the drag gesture remains empty until the finger or cursor has moved. The expected behavior is for the drag gesture to begin even when its translation is of size .zero. This second problem – the nonexistence of a drag gesture once the long press has completed – prevents access to the location of the long-press-then-drag. Access to this location is critical for displaying to the user that the drag interaction has commenced. The below code is based on Apple's example presented here. I've highlighted the failure points in the code with // *. My questions are as follows: What is required to properly update the gesture state? Is it possible to have a viable drag gesture immediately upon fulfilling the long press gesture, even with a translation of .zero? Alternatively to the above question, is there a way to gain access to the location of the long press gesture? import SwiftUI import Charts enum DragState { case inactive case pressing case dragging(translation: CGSize) var isDragging: Bool { switch self { case .inactive, .pressing: return false case .dragging: return true } } } struct ChartGestureOverlay<Value: Comparable & Hashable>: View { @Binding var highlightedValue: Value? let chartProxy: ChartProxy let valueFromChartProxy: (CGFloat, ChartProxy) -> Value? let onDragChange: (DragState) -> Void @GestureState private var dragState = DragState.inactive var body: some View { Rectangle() .fill(Color.clear) .contentShape(Rectangle()) .onTapGesture { location in if let newValue = valueFromChartProxy(location.x, chartProxy) { highlightedValue = newValue } } .gesture(longPressAndDrag) } private var longPressAndDrag: some Gesture { let longPress = LongPressGesture(minimumDuration: 0.2) let drag = DragGesture(minimumDistance: .zero) .onChanged { value in if let newValue = valueFromChartProxy(value.location.x, chartProxy) { highlightedValue = newValue } } return longPress.sequenced(before: drag) .updating($dragState) { value, gestureState, _ in switch value { case .first(true): // * This is never called gestureState = .pressing case .second(true, let drag): // * Drag is often nil // * When drag is nil, we lack access to the location gestureState = .dragging(translation: drag?.translation ?? .zero) default: // * This is never called gestureState = .inactive } onDragChange(gestureState) } } } struct DataPoint: Identifiable { let id = UUID() let category: String let value: Double } struct ContentView: View { let dataPoints = [ DataPoint(category: "A", value: 5), DataPoint(category: "B", value: 3), DataPoint(category: "C", value: 8), DataPoint(category: "D", value: 2), DataPoint(category: "E", value: 7) ] @State private var highlightedCategory: String? = nil @State private var dragState = DragState.inactive var body: some View { VStack { Text("Bar Chart with Gesture Interaction") .font(.headline) .padding() Chart { ForEach(dataPoints) { dataPoint in BarMark( x: .value("Category", dataPoint.category), y: .value("Value", dataPoint.value) ) .foregroundStyle(highlightedCategory == dataPoint.category ? Color.red : Color.gray) .annotation(position: .top) { if highlightedCategory == dataPoint.category { Text("\(dataPoint.value, specifier: "%.1f")") .font(.caption) .foregroundColor(.primary) } } } } .frame(height: 300) .chartOverlay { chartProxy in ChartGestureOverlay<String>( highlightedValue: $highlightedCategory, chartProxy: chartProxy, valueFromChartProxy: { xPosition, chartProxy in if let category: String = chartProxy.value(atX: xPosition) { return category } return nil }, onDragChange: { newDragState in dragState = newDragState } ) } .onChange(of: highlightedCategory, { oldCategory, newCategory in }) } .padding() } } #Preview { ContentView() } Thank you!
7
1
1.4k
Mar ’25
AreaMark Always alignsMarkStylesWithPlotArea for linear gradients
I'm trying to make a Swift Chart where 24 AreaMarks an hour apart on X axis over a day display a vertical gradient. The gradient is vertical and is essentially [Color.opacity(0.1),Colour,Color.opacity(0.1] The idea here is where the upper and lower points of each AreaMark are the same or close to each other in the Y axis, the chart essentially displays a line, where they are far apart you get a nice fading vertical gradient. However, it seems that the .alignsMarkStylesWithPlotArea modifier is always set for AreaMarks even if manually applying it false. Investigating further, I've learnt that with AreaMarks in a series, Swift Charts seems to only listen to the first foreground style set in. I've created some sample code to demonstrate this. struct DemoChartView: View { var body: some View { Chart { AreaMark(x: .value("Time", Date().addingTimeInterval(0)), yStart: .value("1", 40), yEnd: .value("2", 60)) .foregroundStyle(LinearGradient(colors: [.pink, .teal], startPoint: .top, endPoint: .bottom)) .alignsMarkStylesWithPlotArea(false) AreaMark(x: .value("Time", Date().addingTimeInterval(3600)), yStart: .value("1", 44), yEnd: .value("2", 58)) .foregroundStyle(LinearGradient(colors: [.orange, .yellow], startPoint: .top, endPoint: .bottom)) .alignsMarkStylesWithPlotArea(false) AreaMark(x: .value("Time", Date().addingTimeInterval(03600*2)), yStart: .value("1", 50), yEnd: .value("2", 90)) .foregroundStyle(LinearGradient(colors: [.green, .blue], startPoint: .top, endPoint: .bottom)) .alignsMarkStylesWithPlotArea(false) } } } Which produces this: So here, all the different .foregroundStyle LinearGradients are being ignored AND the .alignsMarkStylesWithPlotArea(false) is also ignored - the amount of pink on the first mark is different to the second and third 🤷‍♂️ Has anyone encountered this. Are AreaMarks the correct choice or are they just not setup to create this type of data display. Thanks
3
0
690
Jun ’25
Tranforming a chart's origin
Hi, I'm trying to create a visualization using charts for vision pro. I want to create a line chart that connects pair of points on a donut chart. So i'm trying to draw the lines radially but it seems that the line chart always has only the bottom left corner of the view as origin. How can I tranform the origin to center of the view?
Topic: Design SubTopic: General Tags:
1
0
644
Oct ’24
SwiftUI Charts not working in Xcode 16 Beta 2
Is there a way to workaround this issue? Can I revert back to Beta 1? Failed to build module 'Charts'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.7.41 clang-1600.0.24.1)', while this compiler is 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.11 clang-1600.0.26.2)'). Please select a toolchain which matches the SDK.
2
1
956
Oct ’24
Unable to import Charts in Xcode 16.1 beta 2
Failed to build module 'Charts'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.7.41 clang-1600.0.24.1)', while this compiler is 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.11 clang-1600.0.26.2)'). Please select a toolchain which matches the SDK. Any fix yet?
2
2
714
Oct ’24
VoiceOver ignoring a data series when there are multiple ones
I can't figure out if I've found a VoiceOver problem with Swift Charts or if I'm doing something incorrectly. I have a loop within a loop showing 2 sets of data in the same chart. If I touch a month then VO correctly says there are two data series. But if I keep swiping down only data from the first series is read. ChatGPT said try referencing the outer loop and sure enough that worked if it done in BOTH the label and value. It sounds really awkward though. For example, "High 89 degrees F High October". Below the "bad" chart only says something such as "92 degrees F October" when swiping down. The "good" chart will read the high and low temperature data. VStack { headerText("BAD") Chart { ForEach(processedMonthlyInput) { oneMonth in ForEach(oneMonth.temperatures, id: \.month) { element in LineMark( x: .value("Month", element.month, unit: .month), y: .value("Temperature", element.tempVal.converted(to: .fahrenheit).value) ) .accessibilityLabel("\(element.month.formatted(.dateTime.month(.wide)))") .accessibilityValue(Text("\(element.tempVal.converted(to: tempUnit).formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))")) } .symbol(by: .value("Type", oneMonth.theType)) .foregroundStyle(by: .value("Type", oneMonth.theType)) .interpolationMethod(.catmullRom) } } .frame(maxHeight: paddingAmount) .padding(.horizontal) headerText("GOOD") Chart { ForEach(processedMonthlyInput) { oneMonth in ForEach(oneMonth.temperatures, id: \.month) { element in LineMark( x: .value("Month", element.month, unit: .month), y: .value("Temperature", element.tempVal.converted(to: .fahrenheit).value) ) .accessibilityLabel("\(oneMonth.theType) \(element.month.formatted(.dateTime.month(.wide)))") .accessibilityValue(Text("\(oneMonth.theType) \(element.tempVal.converted(to: tempUnit).formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))")) } .symbol(by: .value("Type", oneMonth.theType)) .foregroundStyle(by: .value("Type", oneMonth.theType)) .interpolationMethod(.catmullRom) } } .frame(maxHeight: paddingAmount) .padding(.horizontal) }
0
0
450
Sep ’24
Unable to compile SwiftUI Charts on Xcode 16.1 Beta 2
Unable to compile app that imports Swift UI Charts SDK on Xcode Version 16.1 beta 2 (16B5014f) with error: Failed to build module 'Charts'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.7.41 clang-1600.0.24.1)', while this compiler is 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.11 clang-1600.0.26.2)'). Please select a toolchain which matches the SDK. FB15161667
5
15
2.2k
Oct ’24
Swift Charts - weak scrolling performance
Hello there! I wanted to give a native scrolling mechanism for the Swift Charts Graph a try and experiment a bit if the scenario that we try to achieve might be possible, but it seems that the Swift Charts scrolling performance is very poor. The graph was created as follows: X-axis is created based on a date range, Y-axis is created based on an integer values between moreless 0-320 value. the graph is scrollable horizontally only (x-axis), The time range (x-axis) for the scrolling content was set to one year from now date (so the user can scroll one year into the past as a minimum visible date (.chartXScale). The X-axis shows 3 hours of data per screen width (.chartXVisibleDomain). The data points for the graph are generated once when screen is about to appear so that the Charts engine can use it (no lazy loading implemented yet). The line data points (LineMark views) consist of 2880 data points distributed every 5 minutes which simulates - two days of continuous data stream that we want to present. The rest of the graph displays no data at all. The performance result: The graph on the initial loading phase is frozen for about 10-15 seconds until the data appears on the graph. Scrolling is very laggy - the CPU usage is 100% and is unacceptable for the end users. If we show no data at all on the graph (so no LineMark views are created at all) - the result is similar - the empty graph scrolling is also very laggy. Below I am sharing a test code: @main struct ChartsTestApp: App { var body: some Scene { WindowGroup { ContentView() Spacer() } } } struct LineDataPoint: Identifiable, Equatable { var id: Int let date: Date let value: Int } actor TestData { func generate(startDate: Date) async -> [LineDataPoint] { var values: [LineDataPoint] = [] for i in 0..<(1440 * 2) { values.append( LineDataPoint( id: i, date: startDate.addingTimeInterval( TimeInterval(60 * 5 * i) // Every 5 minutes ), value: Int.random(in: 1...100) ) ) } return values } } struct ContentView: View { var startDate: Date { return endDate.addingTimeInterval(-3600*24*30*12) // one year into the past from now } let endDate = Date() @State var dataPoints: [LineDataPoint] = [] var body: some View { Chart { ForEach(dataPoints) { item in LineMark( x: .value("Date", item.date), y: .value("Value", item.value), series: .value("Series", "Test") ) } } .frame(height: 200) .chartScrollableAxes(.horizontal) .chartYAxis(.hidden) .chartXScale(domain: startDate...endDate) // one year possibility to scroll back .chartXVisibleDomain(length: 3600 * 3) // 3 hours visible on screen .onAppear { Task { dataPoints = await TestData().generate(startDate: startDate) } } } } I would be grateful for any insights or suggestions on how to improve it or if it's planned to be improved in the future. Currently, I use UIKit CollectionView where we split the graph into smaller chunks of the graph and we present the SwiftUI Chart content in the cells, so we use the scrolling offered there. I wonder if it's possible to use native SwiftUI for such a scenario so that later on we could also implement some kind of lazy loading of the data as the user scrolls into the past.
3
1
895
Jun ’25
Swift Charts Crashing Xcode 16.1 Beta
Feedback FB14988865 Looks like in the latest version of Xcode 16.1 SwiftCharts crashes on launch. Below are the crashlogs. dyld[88826]: Symbol not found: _$s6Charts12BuilderTupleVyxxQp_QPGAA8AxisMarkAARvzAaERzlMc Referenced from: <01BB785A-84AF-3689-A614-DBFEB6A9733F> /Users/xxxx/Library/Developer/CoreSimulator/Devices/A6841249-F73B-45F2-AB68-96F94D75ACF7/data/Containers/Bundle/Application/E88B6681-E933-48AC-920A-150106F12A1F/xxxxx/xxxxx.debug.dylib Expected in: <35624EEC-5BA2-3545-B05D-BABFE6661F1B> /Library/Developer/CoreSimulator/Volumes/iOS_22A5326g/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 18.0.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Charts.framework/Charts It looks related to using AxisMarks to create custom axis.
1
1
636
Sep ’24
Swift Charts Accessibility Grouping
SwiftUI Charts automatically groups accessibility elements on the graph (Double / Date for example) when there's a lot of data, which overrides the accessibilityLabel and value I set for each data point. This makes sense, but how do we modify the chart navigation accessibility readout when this grouping occurs? Here's an example: var body: some View { let salesData: [(Date, Double)] = [ (Date().addingTimeInterval(-1 * 24 * 60 * 60), 1200), (Date().addingTimeInterval(-2 * 24 * 60 * 60), 1500), (Date().addingTimeInterval(-3 * 24 * 60 * 60), 1000), (Date().addingTimeInterval(-4 * 24 * 60 * 60), 500), (Date().addingTimeInterval(-5 * 24 * 60 * 60), 1500), (Date().addingTimeInterval(-6 * 24 * 60 * 60), 1400), (Date().addingTimeInterval(-7 * 24 * 60 * 60), 1300), (Date().addingTimeInterval(-8 * 24 * 60 * 60), 1800), (Date().addingTimeInterval(-9 * 24 * 60 * 60), 500), (Date().addingTimeInterval(-10 * 24 * 60 * 60), 800), (Date().addingTimeInterval(-11 * 24 * 60 * 60), 800), (Date().addingTimeInterval(-12 * 24 * 60 * 60), 1000), (Date().addingTimeInterval(-13 * 24 * 60 * 60), 1500), (Date().addingTimeInterval(-14 * 24 * 60 * 60), 1500), (Date().addingTimeInterval(-15 * 24 * 60 * 60), 900), ] Chart { ForEach(salesData, id: \.0) { date, sales in LineMark( x: .value("Foo", date), y: .value("Bar", sales) ).accessibilityLabel("Foo: \(date.formatted(date: .abbreviated, time: .omitted)) Bar: \(sales.formatted(.currency(code: "USD")))") } } .accessibilityElement(children: .contain) } } I am wondering if there's a protocol, modifier.. or maybe something like UIAccessibilityContainerType.
2
3
837
Aug ’24
Swift Charts 'Pink' chart drawing issue?
On occasion, Swift Charts draws a large pink rectangle over the entire chart area as shown in the screen shot below. I have never been able to reproduce this issue consistently, so I do not know if it is an issue with the data I input into the chart, or an Apple bug. It happens only rarely, for one redraw cycle, then the chart returns to the correct display. Has anyone experienced this issue, and if so, what was the source of the problem and how did you resolve it?
1
1
558
Aug ’24
How do you skip 0 values on x axis in a SwiftUI Chart
I am plotting a SwiftUI chart from a Struct that looks like this: struct BestWeights: Identifiable { let id = UUID() let date: String let maxWeight: Int } I am then creating an array of this Struct that I will use to plot the Chart: private var bestWeights: [BestWeights] { let unformattedMonth = DateFormatter().monthSymbols[month - 1] let formattedMonth = String(unformattedMonth.prefix(3)) bestWeights.append(BestWeights(date: formattedMonth, maxWeight: bestWeightOfPeriod)) //decrementing down one month selectedDate = Calendar.current.date(byAdding: .month, value: -1, to: selectedDate) ?? selectedDate Then I am iterating through bestWeights and plotting them: Chart { ForEach(bestWeights) { bestWeight in LineMark(x: .value("Date",bestWeight.date), y: .value("MaxWeight", bestWeight.maxWeight)) .symbol { Circle() .fill(.blue) .frame(width: 7, height: 7) } } } The problem is that this produces 0 values on the Y axis that scrape the bottom of the LineMark, now I can fix this by not adding all of the bestWeights who's weight is 0 but then I don't get the full x axis I want which is 6 full months, it would show only the number of months as we have records and would jump from February to July etc.. is there any way to remove the 0 weights while keeping the X axis full of dates
1
0
607
Aug ’24
SwiftCharts Not Showing Accurate Data Until Tap Gesture on Chart
I am showing weather data in a TabView where each tab will show the forecast for that day and then an hour-by-hour Swift Charts of the temperature, chance of precipitation, and then dew point for that day. The day and a text description of that forecast correctly displays for each tab. However, the chart will only show the correct data if you perform a tap gesture on the chart. To reproduce this issue, I do the following: Tap on a day in the "Upcoming Week", preferably a day in the middle. Swipe a couple days to the left or right and use the tap gesture on one of the charts. Oddly, other data on the tab is accurate, it is really just within the SwiftChart. I did try doing a horizontal ScrollView instead but I had the same issue. I also followed the demo for SwiftCharts from WWDC. Below is code that shows the TabView: struct UpcomingDaysView: View { let forecastModel:WeatherModel @State var targetPeriod:WeatherForecastDayPeriod var body: some View { TabView(selection:$targetPeriod) { ForEach(forecastModel.dailyPeriods, id: \.self) {period in DailyViewChartDetail(forecastModel: forecastModel, periodToShow: period) .frame(alignment: .topLeading) .tag(period) } } .containerRelativeFrame([.vertical]) .tabViewStyle(.page) .indexViewStyle(.page(backgroundDisplayMode: .always)) .padding(EdgeInsets(top: 20, leading: 0, bottom: 0, trailing: 0)) } } } And here is the DailyViewChartDetail: struct DailyViewChartDetail: View { var forecastModel:WeatherModel var periodToShow:WeatherForecastDayPeriod var body: some View { VStack { Text("\(DateUtility.getLongerDay(date: DateUtility.getDate(stringDate: periodToShow.dayForecast.startTime)))") .font(.title2) Text("\(periodToShow.dayForecast.detailedForecast)") .font(.subheadline) .padding(5) HourlyViewChart(forecastModel: WeatherPeriodUtility.filterModel(forecastModel: forecastModel, targetPeriod: periodToShow)) .padding(5) Spacer() } .frame( alignment: .topLeading ) } } And then some of the more relevant code in the HourlyViewChart struct HourlyViewChart: View { var forecastModel:WeatherModel @State var range: (Date, Date)? = nil @State var rawSelectedDateTemp: Date? = nil @State var rawSelectedDatePrecipitation: Date? = nil @State var rawSelectedDewPoint: Date? = nil // ... more code Below is an image that shows the issue:
2
0
808
Aug ’24
SwiftUI Chart SectorMark Flips Pie Selection On User Input???
Confused as to why the Chart flips with each user input. The console is also output unique id for each slice which was not my intention. Not sure if the unique .id is the culprit behind the flip. selectedCount changed to: Optional(3) Selected slice: Optional(App.EmojiUsage(id: 69090646-0D0A-4FE8-86EC-4103608DC3F7, emojiTab: App.emojiTab.sad, count: 1)) Scheduling reset task to run in 2 seconds Resetting selected slice and count selectedCount changed to: Optional(1) Selected slice: Optional(App.EmojiUsage(id: DE4A76D1-CC57-4FA0-A261-9AD1A6E28F95, emojiTab: App.emojiTab.happy, count: 2)) Scheduling reset task to run in 2 seconds Resetting selected slice and count selectedCount changed to: Optional(3) Selected slice: Optional(App.EmojiUsage(id: 5052F8EA-2582-4E72-A61D-01FCCDF3DB03, emojiTab: App.emojiTab.sad, count: 1)) Scheduling reset task to run in 2 seconds Resetting selected slice and count selectedCount changed to: Optional(0) Selected slice: Optional(App.EmojiUsage(id: 5C1AB577-6CFC-4BA8-A9DF-30822EF79B91, emojiTab: App.emojiTab.happy, count: 2)) Scheduling reset task to run in 2 seconds @Model class AppModel { var id: String var journalEntry: String var date: Date var emojiTab: emojiTab init(journalEntry: String, date: Date, emojiTab: emojiTab) { self.id = UUID().uuidString self.journalEntry = journalEntry self.date = date self.emojiTab = emojiTab } } struct EmojiPrompt: Identifiable { var id = UUID() var icon: RiveViewModel var emojitab: emojiTab var title: String } enum emojiTab: String, Codable, Plottable { case happy case sad case sleep var primitivePlottable: Double { switch self { case .sleep: return 0.0 case .happy: return 1.0 case .sad: return 2.0 } } } var emojiPrompt = [ EmojiPrompt( icon: RiveViewModel( fileName: "app", stateMachineName: "happyBtnSM", artboardName: "happyBtn" ), emojitab: .happy, title: "Happy 1" ), EmojiPrompt( icon: RiveViewModel( fileName: "app", stateMachineName: "sadBtnSM", artboardName: "sadBtn" ), emojitab: .sad, title: "Sad 2" ), EmojiPrompt( icon: RiveViewModel( fileName: "app", stateMachineName: "happyBtnSM", artboardName: "happyBtn" ), emojitab: .sleep, title: "Sleep" ) ] import SwiftUI import SwiftData import RiveRuntime import Charts struct SectorChartView: View { @Environment(\.modelContext) private var context: ModelContext @Binding var selectedEmojiUsage: EmojiUsage? @State private var selectedCount: Int? @Binding var selectedSlice: EmojiUsage? @State private var resetTask: DispatchWorkItem? // State variable for the reset task var emojiUsageData: [EmojiUsage] var resetDelay: TimeInterval = 2.0 // Adjustable delay for reset var body: some View { ZStack { Chart { ForEach(emojiUsageData) { data in SectorMark( angle: .value("Count", data.count), innerRadius: .ratio(0.70), outerRadius: selectedSlice?.emojiTab == data.emojiTab ? .ratio(1.0) : .ratio(0.75), angularInset: 1.5 ) .cornerRadius(4) .foregroundStyle(by: .value("Emoji", data.emojiTab.rawValue.capitalized)) } } .chartAngleSelection(value: $selectedCount) .chartBackground { chartProxy in GeometryReader { geo in let frame = geo[chartProxy.plotFrame!] VStack { if let selectedEmojiUsage = selectedEmojiUsage { RiveViewModel(fileName: "app", stateMachineName: "\(selectedEmojiUsage.emojiTab.rawValue)BtnSM", artboardName: "\(selectedEmojiUsage.emojiTab.rawValue)Btn") .view() .frame(width: 120, height: 120) .id(selectedEmojiUsage.emojiTab.rawValue) // Force re-render when the emojiTab changes } else { RiveViewModel(fileName: "app", stateMachineName: "sleepBtnSM", artboardName: "sleepBtn") .view() .frame(width: 120, height: 120) .id("sleep") // Force re-render when default state } } .position(x: frame.midX, y: frame.midY) } } } .onChange(of: selectedCount) { oldValue, newValue in // Ensure reset task is only scheduled if there is a valid new value guard newValue != nil else { return } resetTask?.cancel() // Cancel any existing reset task print("selectedCount changed to: \(String(describing: newValue))") if let newValue = newValue { withAnimation { getSelectedSlice(value: newValue) } let task = DispatchWorkItem { withAnimation(.easeIn) { print("Resetting selected slice and count") self.selectedSlice = nil self.selectedCount = nil self.selectedEmojiUsage = nil } } resetTask = task print("Scheduling reset task to run in 2 seconds") DispatchQueue.main.asyncAfter(deadline: .now() + resetDelay, execute: task) // Schedule reset after specified delay } } .frame(width: 250, height: 250) } private func getSelectedSlice(value: Int) { var cumulativeTotal = 0 _ = emojiUsageData.first { emojiRange in cumulativeTotal += emojiRange.count if value <= cumulativeTotal { selectedSlice = emojiRange selectedEmojiUsage = emojiRange print("Selected slice: \(String(describing: selectedSlice))") return true } return false } } } var emojiUsageData: [EmojiUsage] { let groupedEntries = Dictionary(grouping: entries, by: { $0.emojiTab }) return groupedEntries.map { (key, value) in EmojiUsage(emojiTab: key, count: value.count) } } struct EmojiUsage: Identifiable { var id = UUID() var emojiTab: emojiTab var count: Int }
3
0
892
Aug ’24
TabView and Swift Charts giving inconsistent behaviour when swiping between pages
Hi there, I have a TabView in page style. Inside that TabView I have a number of views, each view is populated with a model object from an array. The array is iterated to provide the chart data. Here is the code: TabView(selection: $displayedChartIndex) { ForEach((0..<data.count), id: \.self) { index in ZStack { AccuracyLineView(graphData: tabSelectorModel.lineChartModels[index]) .padding(5) } .tag((index)) } } .tabViewStyle(.page) .indexViewStyle(.page(backgroundDisplayMode: .always)) I am seeing odd behaviour, as I swipe left and right, occasionally the chart area shows the chart from another page in the TabView. I know the correct view is being shown as there are text elements. See the screenshot below. The screen on the right is running iOS 17.2 and this works correctly. The screen on the left is running iOS 17.4 and the date at the top is correct which tells me that the data object is correct. However the graph is showing a chart from a different page. When I click on the chart on the left (I have interaction enabled) then it immediately draws the correct chart. If I disable the interaction then I still get the behaviour albeit the chart never corrects itself because there is no interaction! I can reproduce this in the 17.4 simulator and it is happening in my live app on iOS17.4. This has only started happening since iOS 17.4 dropped and works perfectly in iOS 17.2 simulator and I didn't notice it in the live app when I was running 17.3. Is this a bug and/or is there a workaround? For info this is the chart view code, it is not doing anything clever: struct AccuracyLineView: View { @State private var selectedIndex: Int? let graphData: LineChartModel func calcHourMarkers (maxTime: Int) -> [Int] { let secondsInDay = 86400 // 60 * 60 * 24 var marks: [Int] = [] var counter = 0 while counter <= maxTime { if (counter > 0) { marks.append(counter) } counter += secondsInDay } return marks } var selectedGraphMark: GraphMark? { var returnMark: GraphMark? = nil var prevPoint = graphData.points.first for point in graphData.points { if let prevPoint { if let selectedIndex, let lastPoint = graphData.points.last, ((point.interval + prevPoint.interval) / 2 > selectedIndex || point == lastPoint) { if point == graphData.points.last { if selectedIndex > (point.interval + prevPoint.interval) / 2 { returnMark = point } else { returnMark = prevPoint } } else { returnMark = prevPoint break } } } prevPoint = point } return returnMark } var body: some View { let lineColour:Color = Color(AppTheme.globalAccentColour) VStack { HStack { Image(systemName: "clock") Text(graphData.getStartDate() + " - " + graphData.getEndDate()) // 19-29 Sept .font(.caption) .fontWeight(.light) Spacer() } Spacer() Chart { // Lines ForEach(graphData.points) { item in LineMark( x: .value("Interval", item.interval), y: .value("Offset", item.timeOffset), series: .value("A", "A") ) .interpolationMethod(.catmullRom) .foregroundStyle(lineColour) .symbol { Circle() .stroke(Color(Color(UIColor.secondarySystemGroupedBackground)), lineWidth: 4) .fill(AppTheme.globalAccentColour) .frame(width: 10) } } ForEach(graphData.trend) { item in LineMark ( x: .value("Interval", item.interval), y: .value("Offset", item.timeOffset) ) .foregroundStyle(Color(UIColor.systemGray2)) } if let selectedGraphMark { RuleMark(x: .value("Offset", selectedGraphMark.interval)) .foregroundStyle(Color(UIColor.systemGray4)) } } .chartXSelection(value: $selectedIndex) .chartXScale(domain: [0, graphData.getMaxTime()]) } } }
10
0
1.7k
Jan ’25
Swift Charts performance when displaying many data points
I'm currently evaluating Swift Charts to use in my macOS app, where I need to (potentially) display a few millions of data points, ideally all of them at one time. I want to give users the possibility to zoom in & out, so the entire range of values could be displayed at one moment. However, starting at around 20K data points (on my computer), the Chart takes a little bit to set up, but the window resizing is laggy. The performance seems to decrease linearly (?), when dealing with 100K data points you can barely resize the window and the Chart setup/creation is noticeable enough. Dealing with 500K data points is out of the question, the app is pretty much not useable. So I'm wondering if anybody else had a similar issue and what can be done? Is there any "magic" Swift Charts setting that could improve the performance? I have a "data decimation" algorithm, and given no choice I will use it, but somehow I was hoping for Swift Charts to gracefully handle at least 100K data points (there are other libs which do this!). Also, limiting the displayed data range is out of the question for my case, this is a crucial feature of the app. Here's the code that I'm using, but it's the most basic one: struct DataPoint: Identifiable { var id: Double { Double(xValue) } let xValue: Int let yValue: Double } let dataPoints: [DataPoint] = (0..<100_000).map { DataPoint(xValue: $0, yValue: Double($0)) } struct MyChart: View { var body: some View { Chart(dataPoints) { dataPoint in PointMark(x: .value("Index", dataPoint.xValue), y: .value("Y Value", dataPoint.yValue)) } } } Some additional info, if it helps: The Chart is included in a AppKit window via NSHostingController (in my sample project the window contains nothing but the chart) The computer is a MacBook Pro, 2019 and is running macOS 10.14
4
2
1.9k
Sep ’24
Swift charts displaying wrong theme through UIHostingController
Hi there, I'm currently using UIHostingController to display swift charts in uikit. The problem im facing is that the UIHostingController isn't outputting the intended theme. When the simulator/phone is on dark mode the view is still in light mode. Iv'e tried to force the view to use dark mode with: .environment(\.colorScheme, .dark) But it doesn't seem to help. Here's how I implement the UIHostingController to my view: let controller = UIHostingController(rootView: StatVC()) controller.view.translatesAutoresizingMaksIntoConstraints = false addChild(controller) controller.didMove(toParent: self) view.addSubview(controller.view) where StatVC() is the swiftui view which contains the swift chart.
1
0
1.2k
Feb ’25