diff --git a/swiftui-expert-skill/SKILL.md b/swiftui-expert-skill/SKILL.md index 62dc90f..197cb32 100644 --- a/swiftui-expert-skill/SKILL.md +++ b/swiftui-expert-skill/SKILL.md @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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`) @@ -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 diff --git a/swiftui-expert-skill/references/canvas-graphics.md b/swiftui-expert-skill/references/canvas-graphics.md new file mode 100644 index 0000000..5f7b6bb --- /dev/null +++ b/swiftui-expert-skill/references/canvas-graphics.md @@ -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)) + + // 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