@mmerterden/multi-agent-pipeline 20.17.1 → 20.17.2
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 +11 -0
- package/docs/facts.json +6 -6
- package/install/catalog-history.json +1 -1
- package/manifest.json +75 -37
- package/package.json +1 -1
- package/pipeline/skills/.skill-manifest.json +33 -13
- package/pipeline/skills/.skills-index.json +66 -11
- package/pipeline/skills/shared/README.md +18 -13
- package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +3 -1
- package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +2 -1
- package/pipeline/skills/shared/external/hig-app-icons/SKILL.md +159 -0
- package/pipeline/skills/shared/external/hig-app-icons/evals/evals.json +48 -0
- package/pipeline/skills/shared/external/hig-app-icons/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +115 -0
- package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +32 -4
- package/pipeline/skills/shared/external/hig-components-layout/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/hig-components-layout/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +231 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +5 -30
- package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
- package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -4
- package/pipeline/skills/shared/external/hig-foundations/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/hig-foundations/evals/trigger-eval.json +21 -0
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +34 -26
- package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +219 -0
- package/pipeline/skills/shared/external/hig-inputs/SKILL.md +5 -4
- package/pipeline/skills/shared/external/hig-patterns/SKILL.md +19 -3
- package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +188 -0
- package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +5 -11
- package/pipeline/skills/shared/external/hig-platforms/SKILL.md +17 -5
- package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +218 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +4 -1
- package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +135 -0
- package/pipeline/skills/shared/external/ios-design-review/SKILL.md +146 -0
- package/pipeline/skills/shared/external/ios-design-review/evals/evals.json +48 -0
- package/pipeline/skills/shared/external/ios-design-review/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +135 -0
- package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +193 -0
- package/pipeline/skills/shared/external/ios-haptics/SKILL.md +191 -0
- package/pipeline/skills/shared/external/ios-haptics/evals/evals.json +49 -0
- package/pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +215 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md +53 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/SKILL.md +170 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +50 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +212 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +145 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +122 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/SKILL.md +177 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/evals/evals.json +49 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +135 -0
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +38 -50
- package/pipeline/skills/shared/external/swiftui-animation/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/swiftui-animation/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +7 -73
- package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +185 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +6 -6
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +3 -253
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +4 -47
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +3 -111
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +11 -381
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +184 -224
- package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +246 -33
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +227 -0
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +15 -18
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +1 -1
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +6 -68
- package/pipeline/skills/skills-index.md +16 -11
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +0 -786
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Typography, color and spacing: numbers and code
|
|
2
|
+
|
|
3
|
+
Companion to `../SKILL.md` for the `hig-foundations` skill. The numbers below
|
|
4
|
+
come from Apple's Human Interface Guidelines (Typography, Accessibility,
|
|
5
|
+
Color, Layout); the spacing scale is a design convention, marked as such.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
1. [Text sizes and weights](#1-text-sizes-and-weights)
|
|
10
|
+
2. [iOS text styles at the default size](#2-ios-text-styles-at-the-default-size)
|
|
11
|
+
3. [Custom fonts that still scale](#3-custom-fonts-that-still-scale)
|
|
12
|
+
4. [Adapting layout at large sizes](#4-adapting-layout-at-large-sizes)
|
|
13
|
+
5. [Color roles](#5-color-roles)
|
|
14
|
+
6. [Contrast minimums](#6-contrast-minimums)
|
|
15
|
+
7. [Color on Liquid Glass](#7-color-on-liquid-glass)
|
|
16
|
+
8. [Spacing and margins](#8-spacing-and-margins)
|
|
17
|
+
9. [Touch targets](#9-touch-targets)
|
|
18
|
+
|
|
19
|
+
## 1. Text sizes and weights
|
|
20
|
+
|
|
21
|
+
| Platform | Default size | Minimum size |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| iOS, iPadOS | 17 pt | 11 pt |
|
|
24
|
+
| macOS | 13 pt | 10 pt |
|
|
25
|
+
| tvOS | 29 pt | 23 pt |
|
|
26
|
+
| visionOS | 17 pt | 12 pt |
|
|
27
|
+
| watchOS | 16 pt | 12 pt |
|
|
28
|
+
|
|
29
|
+
- The minimum applies to custom fonts too. A thin custom face needs to sit
|
|
30
|
+
above these sizes to stay legible.
|
|
31
|
+
- Prefer Regular, Medium, Semibold or Bold. Avoid Ultralight, Thin and Light,
|
|
32
|
+
which are hard to read, especially at small sizes.
|
|
33
|
+
- Keep the number of typefaces small; build hierarchy with weight, size and
|
|
34
|
+
color inside one family.
|
|
35
|
+
|
|
36
|
+
## 2. iOS text styles at the default size
|
|
37
|
+
|
|
38
|
+
Values at the Large (default) content size. Emphasized weight is what the
|
|
39
|
+
style turns into with `.bold()` in SwiftUI or the bold symbolic trait in UIKit.
|
|
40
|
+
|
|
41
|
+
| SwiftUI style | Weight | Size (pt) | Leading (pt) | Emphasized weight |
|
|
42
|
+
|---|---|---|---|---|
|
|
43
|
+
| `.largeTitle` | Regular | 34 | 41 | Bold |
|
|
44
|
+
| `.title` | Regular | 28 | 34 | Bold |
|
|
45
|
+
| `.title2` | Regular | 22 | 28 | Bold |
|
|
46
|
+
| `.title3` | Regular | 20 | 25 | Semibold |
|
|
47
|
+
| `.headline` | Semibold | 17 | 22 | Semibold |
|
|
48
|
+
| `.body` | Regular | 17 | 22 | Semibold |
|
|
49
|
+
| `.callout` | Regular | 16 | 21 | Semibold |
|
|
50
|
+
| `.subheadline` | Regular | 15 | 20 | Semibold |
|
|
51
|
+
| `.footnote` | Regular | 13 | 18 | Semibold |
|
|
52
|
+
| `.caption` | Regular | 12 | 16 | Semibold |
|
|
53
|
+
| `.caption2` | Regular | 11 | 13 | Semibold |
|
|
54
|
+
|
|
55
|
+
```swift
|
|
56
|
+
Text(order.total, format: .currency(code: order.currencyCode))
|
|
57
|
+
.font(.body)
|
|
58
|
+
.bold()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use the emphasized variant for a second level of hierarchy inside a style
|
|
62
|
+
instead of jumping to a bigger style. Bold Text (an accessibility setting) is
|
|
63
|
+
honoured automatically by system fonts; read `legibilityWeight` when a custom
|
|
64
|
+
drawing needs to follow it.
|
|
65
|
+
|
|
66
|
+
## 3. Custom fonts that still scale
|
|
67
|
+
|
|
68
|
+
A custom face keeps Dynamic Type only when it is tied to a text style:
|
|
69
|
+
|
|
70
|
+
```swift
|
|
71
|
+
extension Font {
|
|
72
|
+
static func brand(_ style: Font.TextStyle, size: CGFloat) -> Font {
|
|
73
|
+
.custom("BrandSans-Regular", size: size, relativeTo: style)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Text("Your trips")
|
|
78
|
+
.font(.brand(.title, size: 28))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`Font.custom(_:size:relativeTo:)` (iOS 14) scales the given size the way the
|
|
82
|
+
named text style scales. `Font.custom(_:size:)` alone also scales relative to
|
|
83
|
+
body; `Font.custom(_:fixedSize:)` does not scale and is only for content that
|
|
84
|
+
must not grow. Numeric layout values next to text use `@ScaledMetric`:
|
|
85
|
+
|
|
86
|
+
```swift
|
|
87
|
+
@ScaledMetric(relativeTo: .body) private var iconSide: CGFloat = 24
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
In UIKit, `UIFontMetrics(forTextStyle:).scaledFont(for:)` with
|
|
91
|
+
`adjustsFontForContentSizeCategory = true` does the same.
|
|
92
|
+
|
|
93
|
+
## 4. Adapting layout at large sizes
|
|
94
|
+
|
|
95
|
+
- Keep truncation to a minimum: aim to show as much useful text at the largest
|
|
96
|
+
accessibility size as at the largest standard size.
|
|
97
|
+
- Switch side-by-side arrangements to stacked ones in horizontally constrained
|
|
98
|
+
places, and reduce the number of columns.
|
|
99
|
+
- Keep primary elements toward the top even at very large sizes.
|
|
100
|
+
- Not every word must grow: tab titles, for example, stay put while content
|
|
101
|
+
grows. Small fixed controls can offer the Large Content Viewer instead
|
|
102
|
+
(see `ios-accessibility`).
|
|
103
|
+
|
|
104
|
+
```swift
|
|
105
|
+
@Environment(\.dynamicTypeSize) private var typeSize
|
|
106
|
+
|
|
107
|
+
var body: some View {
|
|
108
|
+
let layout = typeSize.isAccessibilitySize
|
|
109
|
+
? AnyLayout(VStackLayout(alignment: .leading, spacing: 8))
|
|
110
|
+
: AnyLayout(HStackLayout(spacing: 12))
|
|
111
|
+
layout {
|
|
112
|
+
Text(item.title).font(.headline)
|
|
113
|
+
Text(item.subtitle).font(.subheadline).foregroundStyle(.secondary)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 5. Color roles
|
|
119
|
+
|
|
120
|
+
| Role | SwiftUI |
|
|
121
|
+
|---|---|
|
|
122
|
+
| Labels | `.primary`, `.secondary`, `Color(uiColor: .tertiaryLabel)`, `Color(uiColor: .quaternaryLabel)` |
|
|
123
|
+
| Backgrounds | `Color(uiColor: .systemBackground)`, `.secondarySystemBackground`, `.tertiarySystemBackground` |
|
|
124
|
+
| Grouped backgrounds | `Color(uiColor: .systemGroupedBackground)` and its secondary and tertiary levels |
|
|
125
|
+
| Fills and separators | `Color(uiColor: .systemFill)` and its levels, `Color(uiColor: .separator)` |
|
|
126
|
+
| Accent | `.tint(...)` once near the root; controls pick it up |
|
|
127
|
+
| Destructive | `Button(role: .destructive)` rather than painting text red |
|
|
128
|
+
|
|
129
|
+
- Do not redefine a semantic color's meaning (no `separator` as text color, no
|
|
130
|
+
`secondaryLabel` as a background).
|
|
131
|
+
- Do not hard-code the documented system values; they change between releases.
|
|
132
|
+
- Custom colors live in the asset catalog with light, dark and
|
|
133
|
+
increased-contrast variants.
|
|
134
|
+
- Use one color for one meaning across the app; do not reuse the brand color
|
|
135
|
+
that marks interactive text for non-interactive text.
|
|
136
|
+
- Never carry meaning with color alone: add a symbol or a word.
|
|
137
|
+
|
|
138
|
+
## 6. Contrast minimums
|
|
139
|
+
|
|
140
|
+
From the HIG accessibility page:
|
|
141
|
+
|
|
142
|
+
| Text size | Weight | Minimum contrast ratio |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| Up to 17 pt | All | 4.5:1 |
|
|
145
|
+
| 18 pt and up | All | 3:1 |
|
|
146
|
+
| All | Bold | 3:1 |
|
|
147
|
+
|
|
148
|
+
If the default palette cannot meet these, the app must at least provide a
|
|
149
|
+
higher-contrast scheme when Increase Contrast is on
|
|
150
|
+
(`colorSchemeContrast == .increased`). Check both light and dark appearances.
|
|
151
|
+
|
|
152
|
+
## 7. Color on Liquid Glass
|
|
153
|
+
|
|
154
|
+
Glass takes its color from the content behind it. Toolbars and tab bars keep
|
|
155
|
+
symbols and text monochrome by default, flipping dark or light with what is
|
|
156
|
+
underneath. Add color sparingly: for a primary action, tint the background of
|
|
157
|
+
that one control (as prominent buttons do) rather than coloring symbols on many
|
|
158
|
+
controls. Over colorful content, keep bar items monochrome or pick an accent
|
|
159
|
+
that differs clearly from the content. Implementation is in
|
|
160
|
+
`swiftui-liquid-glass`.
|
|
161
|
+
|
|
162
|
+
## 8. Spacing and margins
|
|
163
|
+
|
|
164
|
+
Start by leaving `spacing:` off stacks; the adaptive default follows the
|
|
165
|
+
platform. When a value is chosen on purpose, a 4 point grid is a widespread
|
|
166
|
+
design convention (Apple does not mandate one). Do not invent values between
|
|
167
|
+
its stops, and prefer the project's spacing tokens when they exist.
|
|
168
|
+
|
|
169
|
+
| Points | Typical use |
|
|
170
|
+
|---|---|
|
|
171
|
+
| 4 | Icon to label, inline badge offset |
|
|
172
|
+
| 8 | Related items in one group |
|
|
173
|
+
| 12 | Row inner padding, label to secondary text |
|
|
174
|
+
| 16 | Standard margin, gap between sections |
|
|
175
|
+
| 24 | Card inner padding, section separators |
|
|
176
|
+
| 32 | Major groups, header to content |
|
|
177
|
+
| 48 | Hero and onboarding layouts |
|
|
178
|
+
|
|
179
|
+
- Use system margins (`.padding()` with no value, layout margins, readable
|
|
180
|
+
content guides) before fixed numbers.
|
|
181
|
+
- Keep content inside the safe area; let only backgrounds bleed with
|
|
182
|
+
`.ignoresSafeArea()` or `.background(_:ignoresSafeAreaEdges:)`.
|
|
183
|
+
- Pin persistent actions with `.safeAreaInset(edge: .bottom)`, or
|
|
184
|
+
`safeAreaBar` on iOS 26 when the bar should get the scroll edge effect.
|
|
185
|
+
- `.contentMargins(_:_:for:)` (iOS 17) keeps reading width comfortable in
|
|
186
|
+
scroll views on iPad and in landscape.
|
|
187
|
+
|
|
188
|
+
```swift
|
|
189
|
+
ScrollView {
|
|
190
|
+
LazyVStack { ForEach(lines) { OrderLineRow(line: $0) } }
|
|
191
|
+
}
|
|
192
|
+
.contentMargins(.horizontal, 20, for: .scrollContent)
|
|
193
|
+
.safeAreaInset(edge: .bottom) {
|
|
194
|
+
Button("Place order", action: placeOrder)
|
|
195
|
+
.buttonStyle(.borderedProminent)
|
|
196
|
+
.padding()
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 9. Touch targets
|
|
201
|
+
|
|
202
|
+
| Platform | Default | Minimum |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| iOS, iPadOS | 44 x 44 pt | 28 x 28 pt |
|
|
205
|
+
| macOS | 28 x 28 pt | 20 x 20 pt |
|
|
206
|
+
| visionOS | 60 x 60 pt | 28 x 28 pt |
|
|
207
|
+
| watchOS | 44 x 44 pt | 28 x 28 pt |
|
|
208
|
+
|
|
209
|
+
Spacing matters as much as size: about 12 points of padding around bezeled
|
|
210
|
+
elements and about 24 around elements without a bezel. Grow the hit area of a
|
|
211
|
+
small visual with `.contentShape(Rectangle())` and a frame rather than
|
|
212
|
+
enlarging the glyph.
|
|
213
|
+
|
|
214
|
+
Sources:
|
|
215
|
+
[Typography](https://developer.apple.com/design/human-interface-guidelines/typography),
|
|
216
|
+
[Accessibility](https://developer.apple.com/design/human-interface-guidelines/accessibility),
|
|
217
|
+
[Color](https://developer.apple.com/design/human-interface-guidelines/color),
|
|
218
|
+
[Layout](https://developer.apple.com/design/human-interface-guidelines/layout),
|
|
219
|
+
[Font.custom(_:size:relativeTo:)](https://developer.apple.com/documentation/swiftui/font/custom(_:size:relativeto:)).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hig-inputs
|
|
3
|
-
description: "Apple HIG for input mapped to SwiftUI:
|
|
3
|
+
description: "Apple HIG for input mapped to SwiftUI: gestures, keyboards, pointer, Apple Pencil, focus, game controllers and Digital Crown. Use when designing or reviewing input on an Apple platform."
|
|
4
4
|
metadata:
|
|
5
5
|
risk: safe
|
|
6
6
|
source: multi-agent-pipeline
|
|
@@ -116,9 +116,9 @@ Hardware keyboard (iPad, Mac, visionOS):
|
|
|
116
116
|
|
|
117
117
|
## Haptics and motion input
|
|
118
118
|
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
- Haptics (when to play them, `sensoryFeedback`, feedback generators, Core
|
|
120
|
+
Haptics) are covered in `ios-haptics`. As an input rule: haptics confirm
|
|
121
|
+
meaningful events, never replace a visible change.
|
|
122
122
|
- Shake is reserved for undo; avoid other shake gestures.
|
|
123
123
|
- Motion-based input (tilt) needs a non-motion alternative.
|
|
124
124
|
|
|
@@ -136,6 +136,7 @@ accessibilityAction(named: "Archive").
|
|
|
136
136
|
- `hig-components-system` for menus, controls and text fields.
|
|
137
137
|
- `swiftui-gestures` for gesture implementation detail.
|
|
138
138
|
- `pencilkit-drawing` for Apple Pencil canvases.
|
|
139
|
+
- `ios-haptics` for haptic feedback.
|
|
139
140
|
|
|
140
141
|
## References
|
|
141
142
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hig-patterns
|
|
3
|
-
description: "Apple HIG interaction patterns mapped to SwiftUI:
|
|
3
|
+
description: "Apple HIG interaction patterns mapped to SwiftUI: onboarding, permission requests, loading, empty and error states, modality, search, settings. Use when designing a UX flow on an Apple platform."
|
|
4
4
|
metadata:
|
|
5
5
|
risk: safe
|
|
6
6
|
source: multi-agent-pipeline
|
|
@@ -29,7 +29,22 @@ Each maps to a pattern in `references/patterns.md`.
|
|
|
29
29
|
|
|
30
30
|
## Details on demand
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
- `references/onboarding-and-states.md`: onboarding rules, TipKit versus an
|
|
33
|
+
up-front flow, when and how to ask for permission, the HIG rules for a
|
|
34
|
+
pre-permission screen (one button that opens the alert), and empty, error
|
|
35
|
+
and loading states with `ContentUnavailableView`.
|
|
36
|
+
- `references/patterns.md`: feedback, undo, search, data entry, settings,
|
|
37
|
+
sharing, accounts and notifications.
|
|
38
|
+
|
|
39
|
+
## First launch and access in one glance
|
|
40
|
+
|
|
41
|
+
- Onboarding is optional, short and interactive; context tips beat a carousel.
|
|
42
|
+
- Ask for access when the feature is first used, not at launch, unless the app
|
|
43
|
+
cannot work without it.
|
|
44
|
+
- A pre-permission screen has one button ("Continue", never "Allow") that
|
|
45
|
+
opens the system alert, and no way around it.
|
|
46
|
+
- Empty states say what would be here and offer the next step; errors say
|
|
47
|
+
what happened and offer Retry while keeping input.
|
|
33
48
|
|
|
34
49
|
## Choosing how to present a task
|
|
35
50
|
|
|
@@ -64,7 +79,8 @@ Expected: sheet with Cancel and Save; discard confirmation when edited.
|
|
|
64
79
|
|
|
65
80
|
- `hig-components-content`, `hig-components-system` for the parts of each pattern.
|
|
66
81
|
- `swiftui-navigation` for push, sheet and deep link implementation.
|
|
67
|
-
- `tipkit` for in-context onboarding.
|
|
82
|
+
- `tipkit` for in-context tips that replace up-front onboarding pages.
|
|
83
|
+
- `ios-haptics` for confirming success or failure with haptics.
|
|
68
84
|
|
|
69
85
|
## References
|
|
70
86
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Onboarding, permission requests, empty and error states
|
|
2
|
+
|
|
3
|
+
Companion to `../SKILL.md` for the `hig-patterns` skill. Guidance follows
|
|
4
|
+
Apple's HIG pages on onboarding, launching, privacy and loading; the SwiftUI
|
|
5
|
+
code is ours.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
1. [Onboarding](#1-onboarding)
|
|
10
|
+
2. [Teaching in context with TipKit](#2-teaching-in-context-with-tipkit)
|
|
11
|
+
3. [Asking for permission](#3-asking-for-permission)
|
|
12
|
+
4. [Pre-permission screens](#4-pre-permission-screens)
|
|
13
|
+
5. [Empty states](#5-empty-states)
|
|
14
|
+
6. [Error states](#6-error-states)
|
|
15
|
+
7. [Loading before the empty state](#7-loading-before-the-empty-state)
|
|
16
|
+
|
|
17
|
+
## 1. Onboarding
|
|
18
|
+
|
|
19
|
+
Ideally people understand the app by using it. When onboarding is needed:
|
|
20
|
+
|
|
21
|
+
- It comes after launch is complete; it is not part of the launch screen.
|
|
22
|
+
- Teach by doing: let people try the action safely rather than read about it.
|
|
23
|
+
- Prefer several context-specific tips over one long up-front flow.
|
|
24
|
+
- Keep any required flow brief; do not ask people to memorize steps.
|
|
25
|
+
- Make a tutorial optional. If someone skips it, do not show it again on the
|
|
26
|
+
next launch, but keep it reachable from help, account or settings.
|
|
27
|
+
- Teach the app, not the device or the system.
|
|
28
|
+
- Postpone nonessential setup; ship defaults good enough to start right away.
|
|
29
|
+
- Let people use the app before asking for ratings, purchases or an account
|
|
30
|
+
where possible.
|
|
31
|
+
- A splash screen, if any, is brief; do not hold people on downloads, and
|
|
32
|
+
leave licensing text to the App Store page.
|
|
33
|
+
|
|
34
|
+
Remember completion with `@AppStorage` and present the flow from the root, not
|
|
35
|
+
from inside a feature screen:
|
|
36
|
+
|
|
37
|
+
```swift
|
|
38
|
+
struct RootView: View {
|
|
39
|
+
@AppStorage("onboarding.completed") private var onboardingCompleted = false
|
|
40
|
+
|
|
41
|
+
var body: some View {
|
|
42
|
+
let showsOnboarding = Binding(
|
|
43
|
+
get: { !onboardingCompleted },
|
|
44
|
+
set: { isPresented in onboardingCompleted = !isPresented }
|
|
45
|
+
)
|
|
46
|
+
MainTabs()
|
|
47
|
+
.fullScreenCover(isPresented: showsOnboarding) {
|
|
48
|
+
OnboardingFlow { onboardingCompleted = true }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Every onboarding page has a visible way to skip or finish; the last page ends
|
|
55
|
+
on the app's first useful screen.
|
|
56
|
+
|
|
57
|
+
## 2. Teaching in context with TipKit
|
|
58
|
+
|
|
59
|
+
For a feature people discover later (filters, a gesture, a new toolbar item),
|
|
60
|
+
a TipKit tip shown next to the control beats an onboarding page. Rules,
|
|
61
|
+
display frequency, invalidation and testing are in the `tipkit` skill.
|
|
62
|
+
|
|
63
|
+
## 3. Asking for permission
|
|
64
|
+
|
|
65
|
+
- Ask only for what the feature needs, and as late as possible: when the
|
|
66
|
+
person first uses the feature that depends on it.
|
|
67
|
+
- Ask at launch only when the app cannot work without it (a navigation app and
|
|
68
|
+
location), where the reason is obvious.
|
|
69
|
+
- If the app needs access before it can function at all, the request can be
|
|
70
|
+
part of onboarding, with a sentence on why and what the person gains.
|
|
71
|
+
- The purpose string (usage description) is a short, complete, specific
|
|
72
|
+
sentence in sentence case, active voice, ending with a period:
|
|
73
|
+
"Your location is used to show nearby gates and walking times."
|
|
74
|
+
- Prefer controls that avoid a prompt altogether when they fit: the location
|
|
75
|
+
button, the photos picker, contact and calendar pickers.
|
|
76
|
+
- After a denial, keep the feature visible with an explanation and a link to
|
|
77
|
+
Settings; never re-prompt in a loop.
|
|
78
|
+
|
|
79
|
+
## 4. Pre-permission screens
|
|
80
|
+
|
|
81
|
+
A custom screen before the system alert is allowed when the context alone does
|
|
82
|
+
not explain the request. HIG rules for it:
|
|
83
|
+
|
|
84
|
+
- **Exactly one button, and it opens the system alert.** Title it "Continue"
|
|
85
|
+
or "Next", never "Allow", which mimics the alert's own button.
|
|
86
|
+
- **No other actions**, such as Close, Cancel or "Not now", unless a legal
|
|
87
|
+
consent requires one. The screen must lead to the alert.
|
|
88
|
+
- **No manipulation.** No incentives, no imitation of the alert, no image of
|
|
89
|
+
the alert, no annotations pointing at the alert's buttons. App Review
|
|
90
|
+
rejects these, and tracking requests are watched especially closely.
|
|
91
|
+
|
|
92
|
+
```swift
|
|
93
|
+
struct CameraPrimer: View {
|
|
94
|
+
let onContinue: () -> Void
|
|
95
|
+
|
|
96
|
+
var body: some View {
|
|
97
|
+
VStack(spacing: 24) {
|
|
98
|
+
Image(systemName: "camera.viewfinder")
|
|
99
|
+
.font(.system(.largeTitle))
|
|
100
|
+
.foregroundStyle(.tint)
|
|
101
|
+
.accessibilityHidden(true)
|
|
102
|
+
Text("Scan boarding passes")
|
|
103
|
+
.font(.title2.bold())
|
|
104
|
+
Text("The camera reads the barcode on a paper pass so you can add it to your trip.")
|
|
105
|
+
.font(.body)
|
|
106
|
+
.multilineTextAlignment(.center)
|
|
107
|
+
.foregroundStyle(.secondary)
|
|
108
|
+
Button("Continue", action: onContinue)
|
|
109
|
+
.buttonStyle(.borderedProminent)
|
|
110
|
+
.controlSize(.large)
|
|
111
|
+
}
|
|
112
|
+
.padding()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`onContinue` triggers the real request, for example
|
|
118
|
+
`AVCaptureDevice.requestAccess(for: .video)`.
|
|
119
|
+
|
|
120
|
+
## 5. Empty states
|
|
121
|
+
|
|
122
|
+
`ContentUnavailableView` (iOS 17) is the standard empty and error view. An
|
|
123
|
+
empty state says what would appear here and offers the next step.
|
|
124
|
+
|
|
125
|
+
```swift
|
|
126
|
+
ContentUnavailableView {
|
|
127
|
+
Label("No trips yet", systemImage: "airplane")
|
|
128
|
+
} description: {
|
|
129
|
+
Text("Trips you book or add with a confirmation code appear here.")
|
|
130
|
+
} actions: {
|
|
131
|
+
Button("Add a trip") { isAddingTrip = true }
|
|
132
|
+
.buttonStyle(.borderedProminent)
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- For search with no results use `ContentUnavailableView.search` or
|
|
137
|
+
`ContentUnavailableView.search(text:)`, which word the message for you.
|
|
138
|
+
- Distinguish first use (nothing created yet: invite creation), filtered to
|
|
139
|
+
nothing (offer to clear filters) and cleared by the person (a quiet
|
|
140
|
+
confirmation is enough).
|
|
141
|
+
- Never hide a tab or section because it is empty; show the empty state in it.
|
|
142
|
+
|
|
143
|
+
## 6. Error states
|
|
144
|
+
|
|
145
|
+
- Say what happened in plain words and what the person can do: "Couldn't
|
|
146
|
+
refresh your trips. Check your connection and try again."
|
|
147
|
+
- Offer Retry for transient failures, and keep everything the person entered.
|
|
148
|
+
- Show errors in place, next to the content that failed; reserve alerts for
|
|
149
|
+
failures that block the task.
|
|
150
|
+
- Keep blame out of the copy and avoid codes unless support needs them; when
|
|
151
|
+
they do, show them in a secondary line.
|
|
152
|
+
|
|
153
|
+
```swift
|
|
154
|
+
ContentUnavailableView {
|
|
155
|
+
Label("Couldn't load trips", systemImage: "wifi.exclamationmark")
|
|
156
|
+
} description: {
|
|
157
|
+
Text("Check your connection and try again.")
|
|
158
|
+
} actions: {
|
|
159
|
+
Button("Try Again") { Task { await reload() } }
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 7. Loading before the empty state
|
|
164
|
+
|
|
165
|
+
Order the states so the screen never flashes "empty" while loading:
|
|
166
|
+
|
|
167
|
+
```swift
|
|
168
|
+
switch state {
|
|
169
|
+
case .loading:
|
|
170
|
+
TripsList(trips: Trip.placeholders)
|
|
171
|
+
.redacted(reason: .placeholder)
|
|
172
|
+
case .loaded(let trips) where trips.isEmpty:
|
|
173
|
+
ContentUnavailableView("No trips yet", systemImage: "airplane")
|
|
174
|
+
case .loaded(let trips):
|
|
175
|
+
TripsList(trips: trips)
|
|
176
|
+
case .failed:
|
|
177
|
+
LoadErrorView(retry: reload)
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Placeholders keep the layout in place; a spinner suits short, global waits.
|
|
182
|
+
Screen-level loading recipes are in `swiftui-ui-patterns`.
|
|
183
|
+
|
|
184
|
+
Sources:
|
|
185
|
+
[HIG: Onboarding](https://developer.apple.com/design/human-interface-guidelines/onboarding),
|
|
186
|
+
[HIG: Privacy](https://developer.apple.com/design/human-interface-guidelines/privacy),
|
|
187
|
+
[HIG: Loading](https://developer.apple.com/design/human-interface-guidelines/loading),
|
|
188
|
+
[ContentUnavailableView](https://developer.apple.com/documentation/swiftui/contentunavailableview).
|
|
@@ -7,10 +7,8 @@ Companion to `../SKILL.md` for the `hig-patterns` skill.
|
|
|
7
7
|
- Launch straight into useful content. The launch screen should look like the
|
|
8
8
|
first frame of the app, not a splash advert.
|
|
9
9
|
- Restore where the person left off (`@SceneStorage` for per-window state).
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
- Ask for permissions at the moment they are needed, with a sentence of
|
|
13
|
-
context before the system prompt. Never ask for everything at launch.
|
|
10
|
+
- Onboarding, permission timing and pre-permission screens:
|
|
11
|
+
[onboarding-and-states.md](onboarding-and-states.md).
|
|
14
12
|
- Let people try the app before creating an account whenever possible.
|
|
15
13
|
|
|
16
14
|
## Waiting: loading and progress
|
|
@@ -25,17 +23,13 @@ Companion to `../SKILL.md` for the `hig-patterns` skill.
|
|
|
25
23
|
|
|
26
24
|
## Nothing to show, or something went wrong
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
with a button to create one.
|
|
31
|
-
- For search with no results use `ContentUnavailableView.search(text:)`.
|
|
32
|
-
- Errors say what happened in plain language and what to do. Offer retry for
|
|
33
|
-
transient failures; keep entered data intact.
|
|
26
|
+
Empty, no-results and error states with `ContentUnavailableView`:
|
|
27
|
+
[onboarding-and-states.md](onboarding-and-states.md).
|
|
34
28
|
|
|
35
29
|
## Feedback and confirmation
|
|
36
30
|
|
|
37
31
|
- Confirm success quietly and in place: a checkmark symbol effect, an updated
|
|
38
|
-
row, a brief haptic (
|
|
32
|
+
row, a brief haptic (see `ios-haptics`).
|
|
39
33
|
- Reserve alerts for failures that block progress.
|
|
40
34
|
- Make destructive actions recoverable (undo, trash) rather than piling on
|
|
41
35
|
confirmation dialogs. Where there is no undo, confirm with a destructive-role
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hig-platforms
|
|
3
|
-
description: "Apple HIG per platform: iPhone, iPad, Mac, Watch, TV and Vision Pro
|
|
3
|
+
description: "Apple HIG per platform: iPhone, iPad, Mac, Watch, TV and Vision Pro, iPad size classes and windowing. Use when a design has to fit a specific Apple platform or adapt across them."
|
|
4
4
|
metadata:
|
|
5
5
|
risk: safe
|
|
6
6
|
source: multi-agent-pipeline
|
|
@@ -36,9 +36,10 @@ The answers usually decide navigation structure before any visual question.
|
|
|
36
36
|
`NavigationStack`.
|
|
37
37
|
- Portrait first, but layouts must not break in landscape or at accessibility
|
|
38
38
|
text sizes.
|
|
39
|
-
- On iOS 26, standard tab bars and toolbars render in Liquid Glass
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
- On iOS 26, standard tab bars and toolbars render in Liquid Glass; bar
|
|
40
|
+
behaviour (minimizing, accessories, the search tab) is in
|
|
41
|
+
`hig-components-layout`.
|
|
42
|
+
- Hinged iPhone hardware (iPhone Duo, iOS 27.1): `ios-iphone-duo`.
|
|
42
43
|
|
|
43
44
|
### iPad
|
|
44
45
|
|
|
@@ -52,6 +53,9 @@ The answers usually decide navigation structure before any visual question.
|
|
|
52
53
|
Pencil where drawing or markup fits.
|
|
53
54
|
- Support multiple scenes when documents or records can be open side by side.
|
|
54
55
|
- iPadOS 26 adds a menu bar; populate it through `.commands` as on the Mac.
|
|
56
|
+
- Size classes, full screen versus windowed mode, minimum window sizes,
|
|
57
|
+
`ViewThatFits`, `AnyLayout` and custom `Layout` types:
|
|
58
|
+
`references/ipad-layout.md`.
|
|
55
59
|
|
|
56
60
|
### Mac
|
|
57
61
|
|
|
@@ -103,7 +107,7 @@ The answers usually decide navigation structure before any visual question.
|
|
|
103
107
|
|------|--------------|
|
|
104
108
|
| List and detail on large screens, stack on small | `NavigationSplitView` (collapses automatically) |
|
|
105
109
|
| Tabs that become a sidebar | `.tabViewStyle(.sidebarAdaptable)` |
|
|
106
|
-
| Layout switches on width | `@Environment(\.horizontalSizeClass)`, `ViewThatFits` |
|
|
110
|
+
| Layout switches on width | `@Environment(\.horizontalSizeClass)`, `ViewThatFits`, `AnyLayout` |
|
|
107
111
|
| Platform-only modifiers | `#if os(macOS)` / `#if os(visionOS)` kept at the edge of a view |
|
|
108
112
|
| Commands shared by menu, shortcut and toolbar | `.commands`, `CommandMenu`, `.keyboardShortcut` |
|
|
109
113
|
| Several windows of the same content | `WindowGroup(for: Order.ID.self)` plus `openWindow` |
|
|
@@ -139,6 +143,14 @@ is usually two views that should share a model instead.
|
|
|
139
143
|
- `hig-inputs` for input-specific behavior.
|
|
140
144
|
- `hig-components-layout` for split views, sidebars and tab bars in detail.
|
|
141
145
|
- `swiftui-navigation` for navigation implementation.
|
|
146
|
+
- `ios-iphone-duo` for hinged iPhone layouts (iPhone Duo).
|
|
147
|
+
|
|
148
|
+
## Details on demand
|
|
149
|
+
|
|
150
|
+
- `references/ipad-layout.md`: iPadOS 26 windows, size classes, choosing
|
|
151
|
+
between `NavigationSplitView`, `ViewThatFits`, `AnyLayout`,
|
|
152
|
+
`containerRelativeFrame` and a custom `Layout`, multiple windows and a test
|
|
153
|
+
matrix.
|
|
142
154
|
|
|
143
155
|
## References
|
|
144
156
|
|