Post

Replies

Boosts

Views

Activity

Swift Charts' `chartScrollTargetBehavior` doesn't work on SDK 27
Symptom: chart (bar chart at least) doesn't snap to beginning of a bar mark when specified with chartScrollTargetBehavior. It could stop anywhere in a bar mark. In the following reproduction, I'm using a bar chart with x values being Date objects, and y values being Double. I attached a ProbeBehavior which runs ValueAlignedChartScrollTargetBehavior(matching: DateComponents(hour: 0), majorAlignment: .page)'s updateTarget, but with some debug info printed out. So essentially, I'm running .chartScrollTargetBehavior(.valueAligned(matching: DateComponents(hour: 0), majorAlignment: .page)). Minimal reproduction import SwiftUI import Charts @main struct ChartSnapApp: App { var body: some Scene { WindowGroup { TestChart() } } } private struct TestChart: View { let calendar: Calendar = .current @State private var scrollX = Date.now var body: some View { let half = 42 let today = calendar.startOfDay(for: .now) let start = calendar.date(byAdding: .day, value: -half, to: today)! let end = calendar.date(byAdding: .day, value: half, to: today)! VStack { Text("Scroll: \(scrollX.formatted(.iso8601)) | secondsFromMidnight: \(scrollX.timeIntervalSince(calendar.startOfDay(for: scrollX)), specifier: "%.3f")") .font(.caption).accessibilityIdentifier("scrollReadout") Chart { ForEach(0 ..< half * 2, id: \.self) { offset in let date = calendar.date(byAdding: .day, value: offset, to: start)! BarMark(x: .value("Date", date, unit: .day), y: .value("Value", 1.0)) } } .chartXScale(domain: start ... end) .chartXVisibleDomain(length: 7 * 24 * 3600) .chartYScale(domain: 0 ... 3) .chartScrollableAxes(.horizontal) .chartScrollTargetBehavior(ProbeBehavior()) .chartScrollPosition(x: $scrollX) .onChange(of: scrollX) { _, new in print("SCROLL \(new.formatted(.iso8601)) secondsFromMidnight=\(new.timeIntervalSince(calendar.startOfDay(for: new)))") } .environment(\.calendar, calendar) .padding(.all, 12) .accessibilityIdentifier("chart") } } } private struct ProbeBehavior: ChartScrollTargetBehavior { func updateTarget(_ target: inout ScrollTarget, context: ChartScrollTargetBehaviorContext) { let before = target.rect let orig = context.originalTarget.rect let beforeDate: Date? = context.chartProxy.value(atX: before.minX) let origDate: Date? = context.chartProxy.value(atX: orig.minX) ValueAlignedChartScrollTargetBehavior(matching: DateComponents(hour: 0), majorAlignment: .page).updateTarget(&target, context: context) let afterDate: Date? = context.chartProxy.value(atX: target.rect.minX) let domain = context.chartProxy.xDomain(dataType: Date.self) let zero = context.chartProxy.value(atX: 0, as: Date.self) let todayPosition = context.chartProxy.position(forX: Calendar.current.startOfDay(for: .now)) let line = "PROBE calendar=\(Calendar.current.identifier) timezone=\(Calendar.current.timeZone.identifier) plotSize=\(context.chartProxy.plotSize) xDomain=\(domain) valueAtZero=\(String(describing: zero)) todayPosition=\(String(describing: todayPosition)) velocity=\(context.velocity) originalRect=\(orig) beforeRect=\(before) afterRect=\(target.rect) originalDate=\(String(describing: origDate)) beforeDate=\(String(describing: beforeDate)) afterDate=\(String(describing: afterDate))\n" print(line) let url = URL.documentsDirectory.appending(path: "probe.log") if !FileManager.default.fileExists(atPath: url.path) { FileManager.default.createFile(atPath: url.path, contents: nil) } if let handle = try? FileHandle(forWritingTo: url) { handle.seekToEndOfFile() handle.write(Data(line.utf8)) try? handle.close() } } } Comparing video, left iOS 26.5 simulator, right iOS 27.0 simulator https://jumpshare.com/s/SVdDV27qHlXdbxMp8mWA
2
0
151
5d
Swift Charts' `chartScrollTargetBehavior` doesn't work on SDK 27
Symptom: chart (bar chart at least) doesn't snap to beginning of a bar mark when specified with chartScrollTargetBehavior. It could stop anywhere in a bar mark. In the following reproduction, I'm using a bar chart with x values being Date objects, and y values being Double. I attached a ProbeBehavior which runs ValueAlignedChartScrollTargetBehavior(matching: DateComponents(hour: 0), majorAlignment: .page)'s updateTarget, but with some debug info printed out. So essentially, I'm running .chartScrollTargetBehavior(.valueAligned(matching: DateComponents(hour: 0), majorAlignment: .page)). Minimal reproduction import SwiftUI import Charts @main struct ChartSnapApp: App { var body: some Scene { WindowGroup { TestChart() } } } private struct TestChart: View { let calendar: Calendar = .current @State private var scrollX = Date.now var body: some View { let half = 42 let today = calendar.startOfDay(for: .now) let start = calendar.date(byAdding: .day, value: -half, to: today)! let end = calendar.date(byAdding: .day, value: half, to: today)! VStack { Text("Scroll: \(scrollX.formatted(.iso8601)) | secondsFromMidnight: \(scrollX.timeIntervalSince(calendar.startOfDay(for: scrollX)), specifier: "%.3f")") .font(.caption).accessibilityIdentifier("scrollReadout") Chart { ForEach(0 ..< half * 2, id: \.self) { offset in let date = calendar.date(byAdding: .day, value: offset, to: start)! BarMark(x: .value("Date", date, unit: .day), y: .value("Value", 1.0)) } } .chartXScale(domain: start ... end) .chartXVisibleDomain(length: 7 * 24 * 3600) .chartYScale(domain: 0 ... 3) .chartScrollableAxes(.horizontal) .chartScrollTargetBehavior(ProbeBehavior()) .chartScrollPosition(x: $scrollX) .onChange(of: scrollX) { _, new in print("SCROLL \(new.formatted(.iso8601)) secondsFromMidnight=\(new.timeIntervalSince(calendar.startOfDay(for: new)))") } .environment(\.calendar, calendar) .padding(.all, 12) .accessibilityIdentifier("chart") } } } private struct ProbeBehavior: ChartScrollTargetBehavior { func updateTarget(_ target: inout ScrollTarget, context: ChartScrollTargetBehaviorContext) { let before = target.rect let orig = context.originalTarget.rect let beforeDate: Date? = context.chartProxy.value(atX: before.minX) let origDate: Date? = context.chartProxy.value(atX: orig.minX) ValueAlignedChartScrollTargetBehavior(matching: DateComponents(hour: 0), majorAlignment: .page).updateTarget(&target, context: context) let afterDate: Date? = context.chartProxy.value(atX: target.rect.minX) let domain = context.chartProxy.xDomain(dataType: Date.self) let zero = context.chartProxy.value(atX: 0, as: Date.self) let todayPosition = context.chartProxy.position(forX: Calendar.current.startOfDay(for: .now)) let line = "PROBE calendar=\(Calendar.current.identifier) timezone=\(Calendar.current.timeZone.identifier) plotSize=\(context.chartProxy.plotSize) xDomain=\(domain) valueAtZero=\(String(describing: zero)) todayPosition=\(String(describing: todayPosition)) velocity=\(context.velocity) originalRect=\(orig) beforeRect=\(before) afterRect=\(target.rect) originalDate=\(String(describing: origDate)) beforeDate=\(String(describing: beforeDate)) afterDate=\(String(describing: afterDate))\n" print(line) let url = URL.documentsDirectory.appending(path: "probe.log") if !FileManager.default.fileExists(atPath: url.path) { FileManager.default.createFile(atPath: url.path, contents: nil) } if let handle = try? FileHandle(forWritingTo: url) { handle.seekToEndOfFile() handle.write(Data(line.utf8)) try? handle.close() } } } Comparing video, left iOS 26.5 simulator, right iOS 27.0 simulator https://jumpshare.com/s/SVdDV27qHlXdbxMp8mWA
Replies
2
Boosts
0
Views
151
Activity
5d