swift-code-reviewer-skill 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -162
- package/README.md +91 -21
- package/SKILL.md +107 -725
- package/bin/install.js +87 -22
- package/package.json +16 -2
- package/references/companion-skills.md +70 -0
- package/skills/README.md +43 -0
- package/skills/swift-concurrency/NOTICE.md +18 -0
- package/skills/swift-concurrency/SKILL.md +235 -0
- package/skills/swift-concurrency/references/actors.md +640 -0
- package/skills/swift-concurrency/references/async-await-basics.md +249 -0
- package/skills/swift-concurrency/references/async-sequences.md +635 -0
- package/skills/swift-concurrency/references/core-data.md +533 -0
- package/skills/swift-concurrency/references/glossary.md +96 -0
- package/skills/swift-concurrency/references/linting.md +38 -0
- package/skills/swift-concurrency/references/memory-management.md +542 -0
- package/skills/swift-concurrency/references/migration.md +721 -0
- package/skills/swift-concurrency/references/performance.md +574 -0
- package/skills/swift-concurrency/references/sendable.md +578 -0
- package/skills/swift-concurrency/references/tasks.md +604 -0
- package/skills/swift-concurrency/references/testing.md +565 -0
- package/skills/swift-concurrency/references/threading.md +452 -0
- package/skills/swift-expert/NOTICE.md +18 -0
- package/skills/swift-expert/SKILL.md +226 -0
- package/skills/swift-expert/references/async-concurrency.md +363 -0
- package/skills/swift-expert/references/memory-performance.md +380 -0
- package/skills/swift-expert/references/protocol-oriented.md +357 -0
- package/skills/swift-expert/references/swiftui-patterns.md +294 -0
- package/skills/swift-expert/references/testing-patterns.md +402 -0
- package/skills/swift-testing/NOTICE.md +18 -0
- package/skills/swift-testing/SKILL.md +295 -0
- package/skills/swift-testing/references/async-testing.md +245 -0
- package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
- package/skills/swift-testing/references/fixtures.md +193 -0
- package/skills/swift-testing/references/integration-testing.md +189 -0
- package/skills/swift-testing/references/migration-xctest.md +301 -0
- package/skills/swift-testing/references/parameterized-tests.md +171 -0
- package/skills/swift-testing/references/snapshot-testing.md +201 -0
- package/skills/swift-testing/references/test-doubles.md +243 -0
- package/skills/swift-testing/references/test-organization.md +231 -0
- package/skills/swiftui-expert-skill/NOTICE.md +18 -0
- package/skills/swiftui-expert-skill/SKILL.md +281 -0
- package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
- package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
- package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
- package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
- package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
- package/skills/swiftui-expert-skill/references/charts.md +602 -0
- package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
- package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
- package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
- package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
- package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
- package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
- package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
- package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
- package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
- package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
- package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
- package/skills/swiftui-expert-skill/references/state-management.md +417 -0
- package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
- package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
- package/skills/swiftui-ui-patterns/SKILL.md +95 -0
- package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
- package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
- package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
- package/skills/swiftui-ui-patterns/references/controls.md +57 -0
- package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
- package/skills/swiftui-ui-patterns/references/focus.md +90 -0
- package/skills/swiftui-ui-patterns/references/form.md +97 -0
- package/skills/swiftui-ui-patterns/references/grids.md +71 -0
- package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
- package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
- package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
- package/skills/swiftui-ui-patterns/references/list.md +86 -0
- package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
- package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
- package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
- package/skills/swiftui-ui-patterns/references/media.md +73 -0
- package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
- package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
- package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
- package/skills/swiftui-ui-patterns/references/performance.md +62 -0
- package/skills/swiftui-ui-patterns/references/previews.md +48 -0
- package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
- package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
- package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
- package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
- package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
- package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
- package/skills/swiftui-ui-patterns/references/theming.md +71 -0
- package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
- package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
- package/templates/agents/swift-code-reviewer.md +78 -0
- package/templates/commands/review.md +56 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
# SwiftUI Charts Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Availability](#availability)
|
|
7
|
+
- [Core APIs](#core-apis)
|
|
8
|
+
- [Chart Types](#chart-types)
|
|
9
|
+
- [Axis Tweaks](#axis-tweaks)
|
|
10
|
+
- [Selection APIs](#selection-apis)
|
|
11
|
+
- [Annotations](#annotations)
|
|
12
|
+
- [ChartProxy and Custom Touch Handling](#chartproxy-and-custom-touch-handling)
|
|
13
|
+
- [Modifier Scope](#modifier-scope)
|
|
14
|
+
- [Styling and Visual Channels](#styling-and-visual-channels)
|
|
15
|
+
- [Composing Multiple Marks](#composing-multiple-marks)
|
|
16
|
+
- [Animating Chart Data](#animating-chart-data)
|
|
17
|
+
- [Best Practices](#best-practices)
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
Swift Charts is Apple's native charting framework for SwiftUI. Use `Chart` with one or more marks to build bar, line, area, point, rule, rectangle, and sector charts. This reference covers the standard 2D chart APIs, axis customization, built-in selection APIs, annotations, and custom touch handling.
|
|
22
|
+
|
|
23
|
+
## Availability
|
|
24
|
+
|
|
25
|
+
Base `Chart`, custom axes, scales, and most marks require iOS 16 or later.
|
|
26
|
+
|
|
27
|
+
- `BarMark`, `LineMark`, `AreaMark`, `PointMark`, `RectangleMark`, and `RuleMark` are available on iOS 16+
|
|
28
|
+
- `SectorMark`, built-in selection, and scrollable chart axes require iOS 17+
|
|
29
|
+
- Data-driven plot types such as `BarPlot` and `LinePlot` require iOS 18+
|
|
30
|
+
- Chart3D and Z-axis APIs exist on iOS 26+; this reference is primarily about 2D `Chart`, with a dedicated Chart3D section below
|
|
31
|
+
|
|
32
|
+
```swift
|
|
33
|
+
if #available(iOS 17, *) {
|
|
34
|
+
// Selection, SectorMark, scrollable axes
|
|
35
|
+
} else {
|
|
36
|
+
// Base Chart, axes, scales, and core marks
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Core APIs
|
|
41
|
+
|
|
42
|
+
### Import the Framework
|
|
43
|
+
|
|
44
|
+
Always check that the file imports `Charts` before using `Chart`, `Chart3D`, `BarMark`, `SectorMark`, or `ChartProxy`.
|
|
45
|
+
|
|
46
|
+
```swift
|
|
47
|
+
import SwiftUI
|
|
48
|
+
import Charts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If chart types are unresolved, the first thing to verify is that `Charts` is imported in that file.
|
|
52
|
+
|
|
53
|
+
### Chart Container
|
|
54
|
+
|
|
55
|
+
`Chart` is the root view. Add one or more marks inside it.
|
|
56
|
+
|
|
57
|
+
```swift
|
|
58
|
+
Chart(sales) { item in
|
|
59
|
+
BarMark(
|
|
60
|
+
x: .value("Month", item.month),
|
|
61
|
+
y: .value("Revenue", item.revenue)
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Data Models Should Be Identifiable
|
|
67
|
+
|
|
68
|
+
Prefer `Identifiable` models for chart data so identity stays stable as data changes.
|
|
69
|
+
|
|
70
|
+
```swift
|
|
71
|
+
struct SalesPoint: Identifiable {
|
|
72
|
+
let id: UUID
|
|
73
|
+
let month: String
|
|
74
|
+
let revenue: Double
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If your model cannot conform to `Identifiable`, provide an explicit id key path:
|
|
79
|
+
|
|
80
|
+
```swift
|
|
81
|
+
Chart(sales, id: \.month) { item in
|
|
82
|
+
BarMark(
|
|
83
|
+
x: .value("Month", item.month),
|
|
84
|
+
y: .value("Revenue", item.revenue)
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Plottable Values
|
|
90
|
+
|
|
91
|
+
Use `.value(_, _)` to describe what each axis value means. Those labels are reused by axes, legends, and accessibility.
|
|
92
|
+
|
|
93
|
+
```swift
|
|
94
|
+
LineMark(
|
|
95
|
+
x: .value("Day", entry.date),
|
|
96
|
+
y: .value("Steps", entry.count)
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Chart Types
|
|
101
|
+
|
|
102
|
+
### BarMark
|
|
103
|
+
|
|
104
|
+
```swift
|
|
105
|
+
BarMark(
|
|
106
|
+
x: .value("Product", product.name),
|
|
107
|
+
y: .value("Units", product.units)
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Stacking via `MarkStackingMethod`: `.standard`, `.normalized`, `.center`, `.unstacked`.
|
|
112
|
+
|
|
113
|
+
### LineMark
|
|
114
|
+
|
|
115
|
+
```swift
|
|
116
|
+
LineMark(
|
|
117
|
+
x: .value("Day", day.date),
|
|
118
|
+
y: .value("Steps", day.count)
|
|
119
|
+
)
|
|
120
|
+
.interpolationMethod(.monotone)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Interpolation methods: `.linear`, `.monotone`, `.cardinal`, `.catmullRom`, `.stepStart`, `.stepCenter`, `.stepEnd`. Cardinal and Catmull-Rom accept optional tension/alpha parameters.
|
|
124
|
+
|
|
125
|
+
### AreaMark
|
|
126
|
+
|
|
127
|
+
```swift
|
|
128
|
+
AreaMark(
|
|
129
|
+
x: .value("Hour", sample.hour),
|
|
130
|
+
y: .value("Temperature", sample.value),
|
|
131
|
+
stacking: .unstacked
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Ranged areas use `yStart`/`yEnd` for bands like min/max or confidence intervals:
|
|
136
|
+
|
|
137
|
+
```swift
|
|
138
|
+
AreaMark(
|
|
139
|
+
x: .value("Day", sample.day),
|
|
140
|
+
yStart: .value("Low", sample.low),
|
|
141
|
+
yEnd: .value("High", sample.high)
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### PointMark
|
|
146
|
+
|
|
147
|
+
```swift
|
|
148
|
+
PointMark(
|
|
149
|
+
x: .value("Time", measurement.time),
|
|
150
|
+
y: .value("Value", measurement.value)
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### RectangleMark
|
|
155
|
+
|
|
156
|
+
```swift
|
|
157
|
+
RectangleMark(
|
|
158
|
+
xStart: .value("Start Day", cell.startDay),
|
|
159
|
+
xEnd: .value("End Day", cell.endDay),
|
|
160
|
+
yStart: .value("Low", cell.low),
|
|
161
|
+
yEnd: .value("High", cell.high)
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### RuleMark
|
|
166
|
+
|
|
167
|
+
```swift
|
|
168
|
+
RuleMark(y: .value("Goal", 10_000))
|
|
169
|
+
.foregroundStyle(.red)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### SectorMark
|
|
173
|
+
|
|
174
|
+
Use `SectorMark` for pie and donut-style charts. `SectorMark` requires iOS 17 or later.
|
|
175
|
+
|
|
176
|
+
```swift
|
|
177
|
+
Chart(expenses) { expense in
|
|
178
|
+
SectorMark(
|
|
179
|
+
angle: .value("Amount", expense.amount),
|
|
180
|
+
innerRadius: .ratio(0.6),
|
|
181
|
+
angularInset: 2
|
|
182
|
+
)
|
|
183
|
+
.foregroundStyle(by: .value("Category", expense.category))
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Use `innerRadius` to turn a pie chart into a donut chart, and `angularInset` to separate slices visually.
|
|
188
|
+
|
|
189
|
+
### Plot Types (iOS 18+)
|
|
190
|
+
|
|
191
|
+
iOS 18 adds data-driven plot wrappers: `AreaPlot`, `BarPlot`, `LinePlot`, `PointPlot`, `RectanglePlot`, `RulePlot`, and `SectorPlot`.
|
|
192
|
+
|
|
193
|
+
`LinePlot` and `AreaPlot` also accept function closures for plotting mathematical functions without discrete data:
|
|
194
|
+
|
|
195
|
+
```swift
|
|
196
|
+
if #available(iOS 18, *) {
|
|
197
|
+
Chart {
|
|
198
|
+
LinePlot(x: "x", y: "sin(x)") { x in
|
|
199
|
+
sin(x)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
.chartXScale(domain: -Double.pi ... Double.pi)
|
|
203
|
+
.chartYScale(domain: -1.5 ... 1.5)
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Use plot types when you want a data-first API surface or need function plotting. The underlying chart families stay the same.
|
|
208
|
+
|
|
209
|
+
### Chart3D (iOS 26+)
|
|
210
|
+
|
|
211
|
+
`Chart3D` is a separate API for 3D chart content. It supports 3D `PointMark`, `RectangleMark`, `RuleMark`, and `SurfacePlot`.
|
|
212
|
+
|
|
213
|
+
```swift
|
|
214
|
+
if #available(iOS 26, *) {
|
|
215
|
+
Chart3D(points) { point in
|
|
216
|
+
PointMark(
|
|
217
|
+
x: .value("X", point.x),
|
|
218
|
+
y: .value("Y", point.y),
|
|
219
|
+
z: .value("Z", point.z)
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
.chart3DPose(.front)
|
|
223
|
+
.chart3DCameraProjection(.perspective)
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`SurfacePlot` visualizes mathematical surfaces by evaluating a two-variable function:
|
|
228
|
+
|
|
229
|
+
```swift
|
|
230
|
+
if #available(iOS 26, *) {
|
|
231
|
+
Chart3D {
|
|
232
|
+
SurfacePlot(x: "x", y: "height", z: "z") { x, z in
|
|
233
|
+
sin(x) * cos(z)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
.chartXScale(domain: -Double.pi ... Double.pi)
|
|
237
|
+
.chartZScale(domain: -Double.pi ... Double.pi)
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Camera and pose configuration:
|
|
242
|
+
|
|
243
|
+
- **Projection**: `.chart3DCameraProjection(.orthographic)` (default, precise measurements) or `.perspective` (depth effect)
|
|
244
|
+
- **Pose presets**: `.chart3DPose(.default)`, `.front`, `.back`, `.left`, `.right`
|
|
245
|
+
- **Custom pose**: `.chart3DPose(azimuth: .degrees(45), inclination: .degrees(30))`
|
|
246
|
+
- On visionOS, Chart3D supports natural 3D interaction gestures for rotation and exploration
|
|
247
|
+
|
|
248
|
+
**Always** gate `Chart3D` with `#available(iOS 26, *)` — it is not available on earlier OS versions.
|
|
249
|
+
|
|
250
|
+
## Axis Tweaks
|
|
251
|
+
|
|
252
|
+
### Axis Visibility and Labels
|
|
253
|
+
|
|
254
|
+
Use `chartXAxis`, `chartYAxis`, `chartXAxisLabel`, and `chartYAxisLabel` on the `Chart` container.
|
|
255
|
+
Axis visibility supports `.automatic`, `.visible`, and `.hidden`.
|
|
256
|
+
|
|
257
|
+
```swift
|
|
258
|
+
Chart(data) { item in
|
|
259
|
+
BarMark(
|
|
260
|
+
x: .value("Month", item.month),
|
|
261
|
+
y: .value("Revenue", item.revenue)
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
.chartXAxis(.visible)
|
|
265
|
+
.chartYAxis(.hidden)
|
|
266
|
+
.chartXAxisLabel("Month")
|
|
267
|
+
.chartYAxisLabel("Revenue")
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Custom Axis Marks
|
|
271
|
+
|
|
272
|
+
Use `AxisMarks` to control tick placement, labels, and grid lines.
|
|
273
|
+
|
|
274
|
+
```swift
|
|
275
|
+
Chart(steps) { day in
|
|
276
|
+
LineMark(
|
|
277
|
+
x: .value("Day", day.date),
|
|
278
|
+
y: .value("Steps", day.count)
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
.chartXAxis {
|
|
282
|
+
AxisMarks(
|
|
283
|
+
preset: .aligned,
|
|
284
|
+
position: .bottom,
|
|
285
|
+
values: .stride(by: .day)
|
|
286
|
+
) {
|
|
287
|
+
AxisGridLine()
|
|
288
|
+
AxisTick(length: .label)
|
|
289
|
+
AxisValueLabel(format: .dateTime.weekday(.abbreviated))
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Useful `AxisMarks` inputs:
|
|
295
|
+
|
|
296
|
+
- `preset`: `.automatic`, `.extended`, `.aligned`, `.inset`
|
|
297
|
+
- `position`: `.automatic`, `.leading`, `.trailing`, `.top`, `.bottom`
|
|
298
|
+
- `values`: `.automatic`, `.automatic(desiredCount:)`, `.stride(by:)`, `.stride(by:count:)`, or an explicit array
|
|
299
|
+
|
|
300
|
+
### Axis Components
|
|
301
|
+
|
|
302
|
+
Within `AxisMarks`, combine the built-in axis components as needed:
|
|
303
|
+
|
|
304
|
+
```swift
|
|
305
|
+
AxisGridLine()
|
|
306
|
+
AxisTick()
|
|
307
|
+
AxisValueLabel()
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`AxisValueLabel` can be tuned for dense axes:
|
|
311
|
+
|
|
312
|
+
```swift
|
|
313
|
+
AxisValueLabel(
|
|
314
|
+
collisionResolution: .greedy(minimumSpacing: 8),
|
|
315
|
+
orientation: .vertical
|
|
316
|
+
)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Label orientations: `.automatic`, `.horizontal`, `.vertical`, `.verticalReversed`.
|
|
320
|
+
|
|
321
|
+
Collision strategies: `.automatic`, `.greedy`, `.greedy(priority:minimumSpacing:)`, `.truncate`, `.disabled`.
|
|
322
|
+
|
|
323
|
+
### Axis Domains and Plot Area Tweaks
|
|
324
|
+
|
|
325
|
+
Use scales when you need explicit axis domains or plot area control.
|
|
326
|
+
|
|
327
|
+
```swift
|
|
328
|
+
Chart(data) { item in
|
|
329
|
+
LineMark(
|
|
330
|
+
x: .value("Index", item.index),
|
|
331
|
+
y: .value("Score", item.score)
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
.chartXScale(domain: 0...30)
|
|
335
|
+
.chartYScale(domain: 0...100)
|
|
336
|
+
.chartPlotStyle { plotArea in
|
|
337
|
+
plotArea
|
|
338
|
+
.background(.gray.opacity(0.08))
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
You can set one axis domain without forcing the other:
|
|
343
|
+
|
|
344
|
+
```swift
|
|
345
|
+
.chartXScale(domain: startDate...endDate)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Scrollable Axes (iOS 17+)
|
|
349
|
+
|
|
350
|
+
For larger datasets, make the plot area scroll and control the visible domain.
|
|
351
|
+
|
|
352
|
+
```swift
|
|
353
|
+
@State private var scrollX = 7
|
|
354
|
+
|
|
355
|
+
Chart(data) { item in
|
|
356
|
+
BarMark(
|
|
357
|
+
x: .value("Day", item.day),
|
|
358
|
+
y: .value("Value", item.value)
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
.chartScrollableAxes(.horizontal)
|
|
362
|
+
.chartXVisibleDomain(length: 7)
|
|
363
|
+
.chartScrollPosition(x: $scrollX)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Selection APIs
|
|
367
|
+
|
|
368
|
+
### Single-Value Selection
|
|
369
|
+
|
|
370
|
+
Use `chartXSelection(value:)` or `chartYSelection(value:)` for one selected value.
|
|
371
|
+
|
|
372
|
+
```swift
|
|
373
|
+
@State private var selectedDate: Date?
|
|
374
|
+
|
|
375
|
+
Chart(steps) { day in
|
|
376
|
+
LineMark(x: .value("Day", day.date), y: .value("Steps", day.count))
|
|
377
|
+
|
|
378
|
+
if let selectedDate {
|
|
379
|
+
RuleMark(x: .value("Selected Day", selectedDate))
|
|
380
|
+
.foregroundStyle(.secondary)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
.chartXSelection(value: $selectedDate)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Range Selection
|
|
387
|
+
|
|
388
|
+
Use `chartXSelection(range:)` or `chartYSelection(range:)` for a dragged range. Bind to a `ClosedRange` whose bound type matches the plotted axis value.
|
|
389
|
+
|
|
390
|
+
```swift
|
|
391
|
+
@State private var selectedWeeks: ClosedRange<Int>?
|
|
392
|
+
|
|
393
|
+
Chart(weeks) { week in
|
|
394
|
+
BarMark(x: .value("Week", week.index), y: .value("Revenue", week.revenue))
|
|
395
|
+
}
|
|
396
|
+
.chartXSelection(range: $selectedWeeks)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Choosing Single vs Range
|
|
400
|
+
|
|
401
|
+
- Use `value:` bindings when only one point or axis value should be selected.
|
|
402
|
+
- Use `range:` bindings when users should brush a span (for zoom windows, comparisons, or grouped summaries).
|
|
403
|
+
|
|
404
|
+
### Angle Selection
|
|
405
|
+
|
|
406
|
+
Use `chartAngleSelection(value:)` with `SectorMark` charts. No built-in range overload for angle selection.
|
|
407
|
+
|
|
408
|
+
```swift
|
|
409
|
+
@State private var selectedAmount: Double?
|
|
410
|
+
|
|
411
|
+
Chart(expenses) { expense in
|
|
412
|
+
SectorMark(angle: .value("Amount", expense.amount))
|
|
413
|
+
.foregroundStyle(by: .value("Category", expense.category))
|
|
414
|
+
}
|
|
415
|
+
.chartAngleSelection(value: $selectedAmount)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Important**: Selection bindings return the plottable axis value, not the full data element. Map back to your model if you need the selected record.
|
|
419
|
+
|
|
420
|
+
## Annotations
|
|
421
|
+
|
|
422
|
+
Use `annotation(position:)` on a mark when you need labels, callouts, or highlighted values attached to the plotted content.
|
|
423
|
+
|
|
424
|
+
```swift
|
|
425
|
+
BarMark(
|
|
426
|
+
x: .value("Month", item.month),
|
|
427
|
+
y: .value("Revenue", item.revenue)
|
|
428
|
+
)
|
|
429
|
+
.annotation(position: .top) {
|
|
430
|
+
Text(item.revenue.formatted())
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
This is useful for selected values, thresholds, summaries, and direct labeling. Common positions include `.overlay`, `.top`, `.bottom`, `.leading`, and `.trailing`.
|
|
435
|
+
|
|
436
|
+
## ChartProxy and Custom Touch Handling
|
|
437
|
+
|
|
438
|
+
Use `chartOverlay`/`chartBackground` (iOS 16+) or `chartGesture` (iOS 17+) with `ChartProxy` when built-in selection modifiers are not enough.
|
|
439
|
+
|
|
440
|
+
```swift
|
|
441
|
+
.chartOverlay { proxy in
|
|
442
|
+
GeometryReader { geometry in
|
|
443
|
+
Rectangle().fill(.clear).contentShape(Rectangle())
|
|
444
|
+
.gesture(
|
|
445
|
+
DragGesture(minimumDistance: 0)
|
|
446
|
+
.onChanged { value in
|
|
447
|
+
guard let plotFrame = proxy.plotFrame else { return } // iOS 16: use proxy.plotAreaFrame
|
|
448
|
+
let frame = geometry[plotFrame]
|
|
449
|
+
let x = value.location.x - frame.origin.x
|
|
450
|
+
guard x >= 0, x <= frame.size.width else { return }
|
|
451
|
+
selectedDate = proxy.value(atX: x, as: Date.self)
|
|
452
|
+
}
|
|
453
|
+
.onEnded { _ in selectedDate = nil }
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Use `proxy.plotFrame` (iOS 17+) or `proxy.plotAreaFrame` (iOS 16) to get the plot area anchor.
|
|
460
|
+
|
|
461
|
+
`ChartProxy` gives you lower-level access to:
|
|
462
|
+
|
|
463
|
+
- `value(atX:as:)`, `value(atY:as:)`, and `value(at:as:)` for converting gesture coordinates into chart values
|
|
464
|
+
- `position(forX:)`, `position(forY:)`, and `position(for:)` for placing custom overlays or indicators
|
|
465
|
+
- `selectXValue(at:)`, `selectYValue(at:)`, `selectXRange(from:to:)`, and `selectYRange(from:to:)` for driving built-in selection from custom gestures
|
|
466
|
+
- `plotFrame` (iOS 17+) or `plotAreaFrame` (iOS 16) with `plotSize` for converting between gesture coordinates and the plot area
|
|
467
|
+
|
|
468
|
+
`select*` ChartProxy selection methods and `chartGesture` are available on iOS 17+.
|
|
469
|
+
|
|
470
|
+
## Modifier Scope
|
|
471
|
+
|
|
472
|
+
Apply chart-wide modifiers to the `Chart` container and mark-specific modifiers to the individual mark.
|
|
473
|
+
|
|
474
|
+
```swift
|
|
475
|
+
Chart(data) { item in
|
|
476
|
+
LineMark(
|
|
477
|
+
x: .value("Day", item.date),
|
|
478
|
+
y: .value("Value", item.value)
|
|
479
|
+
)
|
|
480
|
+
.interpolationMethod(.monotone) // Mark-level modifier
|
|
481
|
+
}
|
|
482
|
+
.chartXAxis { AxisMarks() } // Chart-level modifier
|
|
483
|
+
.chartYScale(domain: 0...100) // Chart-level modifier
|
|
484
|
+
.chartPlotStyle { $0.background(.thinMaterial) }
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Styling and Visual Channels
|
|
488
|
+
|
|
489
|
+
### Categorical Coloring
|
|
490
|
+
|
|
491
|
+
Use `foregroundStyle(by: .value(...))` to color marks by a data property. Swift Charts generates a legend automatically.
|
|
492
|
+
|
|
493
|
+
```swift
|
|
494
|
+
Chart(sales) { item in
|
|
495
|
+
BarMark(
|
|
496
|
+
x: .value("Month", item.month),
|
|
497
|
+
y: .value("Revenue", item.revenue)
|
|
498
|
+
)
|
|
499
|
+
.foregroundStyle(by: .value("Region", item.region))
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Avoid** applying `.foregroundStyle(.red)` per mark for categorical data — this suppresses the automatic legend and breaks accessibility.
|
|
504
|
+
|
|
505
|
+
### Custom Color Scales
|
|
506
|
+
|
|
507
|
+
Use `chartForegroundStyleScale` to control the mapping from data values to colors.
|
|
508
|
+
|
|
509
|
+
```swift
|
|
510
|
+
.chartForegroundStyleScale([
|
|
511
|
+
"North": .blue,
|
|
512
|
+
"South": .orange,
|
|
513
|
+
"East": .green
|
|
514
|
+
])
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
For dynamic data where not all series appear at every point, use the mapping overload:
|
|
518
|
+
|
|
519
|
+
```swift
|
|
520
|
+
.chartForegroundStyleScale(domain: regions, mapping: { region in
|
|
521
|
+
colorForRegion(region)
|
|
522
|
+
})
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Symbol and Size Channels
|
|
526
|
+
|
|
527
|
+
Use `symbol(by:)` and `symbolSize(by:)` to encode additional data dimensions on `PointMark` and `LineMark`.
|
|
528
|
+
|
|
529
|
+
```swift
|
|
530
|
+
Chart(measurements) { item in
|
|
531
|
+
PointMark(
|
|
532
|
+
x: .value("Time", item.time),
|
|
533
|
+
y: .value("Value", item.value)
|
|
534
|
+
)
|
|
535
|
+
.foregroundStyle(by: .value("Category", item.category))
|
|
536
|
+
.symbol(by: .value("Category", item.category))
|
|
537
|
+
.symbolSize(by: .value("Weight", item.weight))
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Legend Control
|
|
542
|
+
|
|
543
|
+
```swift
|
|
544
|
+
.chartLegend(.visible)
|
|
545
|
+
.chartLegend(.hidden)
|
|
546
|
+
.chartLegend(position: .bottom, alignment: .center)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Composing Multiple Marks
|
|
550
|
+
|
|
551
|
+
Combine different mark types inside the same `Chart` closure:
|
|
552
|
+
|
|
553
|
+
```swift
|
|
554
|
+
// Line with points
|
|
555
|
+
LineMark(x: .value("Day", day.date), y: .value("Steps", day.count))
|
|
556
|
+
.interpolationMethod(.monotone)
|
|
557
|
+
PointMark(x: .value("Day", day.date), y: .value("Steps", day.count))
|
|
558
|
+
|
|
559
|
+
// Bars with threshold line
|
|
560
|
+
BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue))
|
|
561
|
+
RuleMark(y: .value("Target", 10_000))
|
|
562
|
+
.foregroundStyle(.red)
|
|
563
|
+
.lineStyle(StrokeStyle(dash: [5, 3]))
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## Animating Chart Data
|
|
567
|
+
|
|
568
|
+
Chart marks animate automatically when data identity is stable and changes are wrapped in an animation.
|
|
569
|
+
|
|
570
|
+
```swift
|
|
571
|
+
withAnimation(.easeInOut) {
|
|
572
|
+
chartData = updatedData
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Always** use `Identifiable` models (or explicit `id:`) so Swift Charts can match old and new data points and animate transitions between them.
|
|
577
|
+
|
|
578
|
+
## Best Practices
|
|
579
|
+
|
|
580
|
+
### Do
|
|
581
|
+
|
|
582
|
+
- Use semantic `.value(_, _)` labels so axes and accessibility read clearly
|
|
583
|
+
- Prefer `Identifiable` models (or explicit `id:`) for stable chart data identity
|
|
584
|
+
- Use `foregroundStyle(by:)` for categorical series to get automatic legends and accessibility
|
|
585
|
+
- Use `RuleMark` for goals, thresholds, and selected-value indicators
|
|
586
|
+
- Use explicit `AxisMarks(values:)` when automatic tick generation gets crowded
|
|
587
|
+
- Use `chartXScale` and `chartYScale` when you need stable visual comparisons
|
|
588
|
+
- Use `chartXSelection(range:)` or `chartYSelection(range:)` for brushed selection
|
|
589
|
+
- Gate iOS 17+ APIs such as `SectorMark` and selection with `#available`
|
|
590
|
+
|
|
591
|
+
### Don't
|
|
592
|
+
|
|
593
|
+
- Put chart-wide modifiers such as `chartXAxis` or `chartXSelection` on individual marks
|
|
594
|
+
- Apply manual `.foregroundStyle(.color)` per mark for categorical data — use `foregroundStyle(by:)` instead
|
|
595
|
+
- Rely on unstable identities when chart data can be inserted, removed, or reordered
|
|
596
|
+
- Use string values for naturally numeric or date-based axes unless you want categorical behavior
|
|
597
|
+
- Stack unrelated series by default just because `BarMark` and `AreaMark` allow it
|
|
598
|
+
- Force every tick label to display when collision handling or stride values would be clearer
|
|
599
|
+
- Assume selection returns a model object; it only returns the plottable axis value
|
|
600
|
+
- Forget that range selection is available only for X and Y axes, not angle selection
|
|
601
|
+
|
|
602
|
+
For chart accessibility (VoiceOver, Audio Graph, `AXChartDescriptorRepresentable`), fallback strategies, WWDC sessions, and a full summary checklist, see `charts-accessibility.md`.
|