Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion swiftui-expert-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: swiftui-expert-skill
description: Write, review, or improve SwiftUI code following best practices for state management, view composition, performance, macOS-specific APIs, and iOS 26+ Liquid Glass adoption. Use when building new SwiftUI features, refactoring existing views, reviewing code quality, or adopting modern SwiftUI patterns.
description: Write, review, or improve SwiftUI code following best practices for state management, view composition, performance, macOS-specific APIs, Canvas/Graphics drawing, and iOS 26+ Liquid Glass adoption. Use when building new SwiftUI features, refactoring existing views, reviewing code quality, drawing custom shapes/graphics, or adopting modern SwiftUI patterns. Trigger whenever the user mentions "SwiftUI", "Canvas", "GraphicsContext", "Shape", or asks to build iOS/macOS UI.
---

# SwiftUI Expert Skill
Expand All @@ -19,6 +19,7 @@ Use this skill to build, review, or improve SwiftUI features with correct state
- Check animation patterns for correctness (see `references/animation-basics.md`, `references/animation-transitions.md`)
- Review accessibility: proper grouping, traits, Dynamic Type support (see `references/accessibility-patterns.md`)
- Check chart patterns for correct mark usage, stable data identity, and availability gating (see `references/charts.md`; for accessibility and fallback strategies see `references/charts-accessibility.md`)
- Check Canvas and GraphicsContext usage for performance and accessibility (see `references/canvas-graphics.md`)
- For macOS targets: verify correct use of macOS-specific APIs and patterns (see `references/macos-scenes.md`, `references/macos-window-styling.md`, `references/macos-views.md`)
- Inspect Liquid Glass usage for correctness and consistency (see `references/liquid-glass.md`)
- Validate iOS 26+ availability handling with sensible fallbacks
Expand All @@ -32,6 +33,7 @@ Use this skill to build, review, or improve SwiftUI features with correct state
- Improve animation patterns (use value parameter, proper transitions, see `references/animation-basics.md`, `references/animation-transitions.md`)
- Improve accessibility: use `Button` over tap gestures, add `@ScaledMetric` for Dynamic Type (see `references/accessibility-patterns.md`)
- Review chart code for correct modifier scope, styling, and accessibility (see `references/charts.md`, `references/charts-accessibility.md`)
- Optimize Canvas rendering with `opaque` and `rendersAsynchronously` (see `references/canvas-graphics.md`)
- For macOS targets: adopt macOS-specific APIs (MenuBarExtra, Settings, Table, Commands, etc.) where appropriate (see `references/macos-scenes.md`, `references/macos-window-styling.md`, `references/macos-views.md`)
- Suggest image downsampling when `UIImage(data:)` is used (as optional optimization, see `references/image-optimization.md`)
- Adopt Liquid Glass only when explicitly requested by the user
Expand All @@ -44,6 +46,7 @@ Use this skill to build, review, or improve SwiftUI features with correct state
- Use correct animation patterns (implicit vs explicit, transitions, see `references/animation-basics.md`, `references/animation-transitions.md`, `references/animation-advanced.md`)
- Use `Button` for tappable elements, add accessibility grouping and labels (see `references/accessibility-patterns.md`)
- For charts: use correct mark types, stable data identity, and gate iOS 17+/18+/26+ APIs (see `references/charts.md`; for accessibility see `references/charts-accessibility.md`)
- Use Canvas for complex, non-interactive 2D graphics (see `references/canvas-graphics.md`)
- For macOS targets: use macOS-specific scenes (see `references/macos-scenes.md`), window styling (see `references/macos-window-styling.md`), and views like HSplitView, Table (see `references/macos-views.md`)
- Apply glass effects after layout/appearance modifiers (see `references/liquid-glass.md`)
- Gate iOS 26+ features with `#available` and provide fallbacks
Expand Down Expand Up @@ -100,6 +103,16 @@ Use this skill to build, review, or improve SwiftUI features with correct state
- Use `.keyframeAnimator` for precise timing control (iOS 17+)
- Animation completion handlers need `.transaction(value:)` for reexecution
- Implicit animations override explicit animations (later in view tree wins)
- Animation completion handlers need `.transaction(value:)` for reexecution

### Canvas and Graphics
- Use `Canvas` for complex, dynamic 2D graphics that don't require individual element interactivity
- Set `opaque: true` for performance if the canvas has no transparency
- Enable `rendersAsynchronously: true` for complex drawings to prevent main thread stalls
- Use `symbols` to reuse SwiftUI views as drawing elements within a canvas
- `GraphicsContext` is a value type; copy it to perform independent operations (clipping, transforms)
- Provide a high-level `accessibilityLabel` for the entire `Canvas` (internal elements are not accessible)
- Pre-calculate or cache complex paths outside the renderer closure when possible

### Accessibility
- Prefer `Button` over `onTapGesture` for tappable elements (free VoiceOver support)
Expand Down Expand Up @@ -233,6 +246,14 @@ Button("Confirm") { }
- [ ] Meaningful `.value()` labels used for axes and accessibility
- [ ] `foregroundStyle(by:)` used for categorical series (not manual per-mark colors)

### Canvas and Graphics (see `references/canvas-graphics.md`)
- [ ] `opaque: true` used if no transparency is needed
- [ ] `rendersAsynchronously: true` used for complex drawings
- [ ] `symbols` used for reusing SwiftUI views
- [ ] `GraphicsContext` copied for independent state changes
- [ ] High-level `accessibilityLabel` provided for the `Canvas`
- [ ] Complex paths pre-calculated or cached outside the renderer

### macOS APIs (see `references/macos-scenes.md`, `references/macos-window-styling.md`, `references/macos-views.md`)
- [ ] Using `Settings` scene for preferences (not a custom window)
- [ ] Using `MenuBarExtra` for menu bar items (not AppKit `NSStatusItem`)
Expand Down Expand Up @@ -264,6 +285,7 @@ Button("Confirm") { }
- `references/animation-advanced.md` - Transactions, phase/keyframe animations (iOS 17+), completion handlers (iOS 17+), `@Animatable` macro (iOS 26+)
- `references/charts.md` - Swift Charts marks, axes, selection, styling, composition, and Chart3D (iOS 26+)
- `references/charts-accessibility.md` - Charts accessibility (VoiceOver, Audio Graph, AXChartDescriptorRepresentable), fallback strategies, and WWDC sessions
- `references/canvas-graphics.md` - SwiftUI Canvas, GraphicsContext, performance optimizations, and symbols
- `references/sheet-navigation-patterns.md` - Sheet presentation, NavigationSplitView, Inspector, and navigation patterns
- `references/scroll-patterns.md` - ScrollView patterns and programmatic scrolling
- `references/image-optimization.md` - AsyncImage, image downsampling, and optimization
Expand Down
118 changes: 118 additions & 0 deletions swiftui-expert-skill/references/canvas-graphics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# SwiftUI Canvas and GraphicsContext Reference

## Table of Contents

- [Overview](#overview)
- [Performance Optimization](#performance-optimization)
- [Using Symbols](#using-symbols)
- [GraphicsContext State](#graphicscontext-state)
- [Best Practices](#best-practices)
- [Summary Checklist](#summary-checklist)

## Overview

`Canvas` provides an immediate mode 2D drawing environment in SwiftUI, passing a `GraphicsContext` to its renderer closure. It is ideal for complex, dynamic graphics that don't require individual element interactivity or accessibility.

```swift
Canvas { context, size in
context.fill(
Path(ellipseIn: CGRect(origin: .zero, size: size)),
with: .color(.blue))
}
.frame(width: 200, height: 200)
```

## Performance Optimization

### 1. Opaque Canvas

If your drawing doesn't require transparency, set `opaque` to `true`. This allows the system to optimize rendering by skipping alpha blending.

```swift
// GOOD - optimized for non-transparent backgrounds
Canvas(opaque: true) { context, size in
context.fill(Path(CGRect(origin: .zero, size: size)), with: .color(.white))
// ... drawing commands
}
```

### 2. Asynchronous Rendering

For complex drawings that might block the main thread, enable `rendersAsynchronously`. This allows the canvas to present its contents in a non-blocking manner.

```swift
// GOOD - prevents main thread stalls for complex graphics
Canvas(rendersAsynchronously: true) { context, size in
// Expensive drawing operations
}
```

### 3. Path Caching

Avoid recreating complex paths inside the renderer closure if they don't change. While `Canvas` is immediate mode, pre-calculating paths outside the renderer can save CPU cycles.

```swift
// BAD - recreating path every frame
Canvas { context, size in
let path = Path { p in /* complex path construction */ }
context.fill(path, with: .color(.red))
}

// GOOD - pre-calculating or caching path
let cachedPath = Path { p in /* complex path construction */ }
Canvas { context, size in
context.fill(cachedPath, with: .color(.red))
}
```

## Using Symbols

Symbols allow you to reuse SwiftUI views as drawing elements within a `Canvas`. This is more efficient than drawing complex shapes manually when those shapes can be represented as views.

```swift
Canvas { context, size in
if let mark = context.resolveSymbol(id: "mark") {
context.draw(mark, at: CGPoint(x: size.width / 2, y: size.height / 2))
}
} symbols: {
Circle()
.fill(.red)
.frame(width: 20, height: 20)
.tag("mark")
}
```

**Note**: Views passed as symbols lose their interactivity and accessibility within the `Canvas`.

## GraphicsContext State

`GraphicsContext` is a value type. To perform independent operations like clipping or transforms without affecting the rest of the drawing, create a copy of the context.

```swift
Canvas { context, size in
// Create a copy for a specific operation
var maskedContext = context
maskedContext.clip(to: Path(CGRect(x: 0, y: 0, width: 50, height: 50)))
maskedContext.fill(Path(Circle(in: CGRect(origin: .zero, size: size))), with: .color(.green))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a valid shape-to-Path conversion in example

The GraphicsContext sample uses Path(Circle(in: ...)), but Circle has no init(in:), so this snippet does not compile if copied into a SwiftUI project. Since this reference is used to drive generated guidance, an invalid API call here can cause downstream code suggestions to fail to build; use a valid conversion like Path(ellipseIn:) or Circle().path(in:) instead.

Useful? React with 👍 / 👎.


// Original context remains unaffected
context.fill(Path(CGRect(x: 60, y: 60, width: 40, height: 40)), with: .color(.blue))
}
```

## Best Practices

- **Use Canvas for Performance**: Prefer `Canvas` over many individual `Shape` views when drawing hundreds or thousands of elements (e.g., particle systems, complex charts).
- **Avoid Interactivity**: Do not use `Canvas` if you need users to interact with individual drawn elements. Use standard SwiftUI views instead.
- **Accessibility**: `Canvas` is treated as a single image by accessibility tools. Provide a high-level `accessibilityLabel` for the entire `Canvas` rather than trying to make internal elements accessible.
- **Coordinate System**: `Canvas` uses a standard 2D coordinate system where `(0,0)` is the top-left corner. Use the `size` parameter to make your drawing responsive.

## Summary Checklist

- [ ] Use `opaque: true` if no transparency is needed
- [ ] Enable `rendersAsynchronously: true` for complex, expensive drawings
- [ ] Use `symbols` to reuse SwiftUI views as drawing elements
- [ ] Copy `GraphicsContext` for independent state changes (clipping, transforms)
- [ ] Pre-calculate or cache complex paths outside the renderer when possible
- [ ] Provide a high-level `accessibilityLabel` for the entire `Canvas`
- [ ] Use `Canvas` only when individual element interactivity is not required