mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-06 01:03:32 +08:00
Merge pull request #250 from OkminLee/feat/skills/ios26-three-skills
LGTM — 3 iOS 26 skills. Pure documentation.
This commit is contained in:
@@ -143,7 +143,7 @@ For manual install instructions see the README in the `rules/` folder.
|
|||||||
/plugin list everything-claude-code@everything-claude-code
|
/plugin list everything-claude-code@everything-claude-code
|
||||||
```
|
```
|
||||||
|
|
||||||
✨ **That's it!** You now have access to 13 agents, 44 skills, and 32 commands.
|
✨ **That's it!** You now have access to 13 agents, 48 skills, and 32 commands.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -252,6 +252,10 @@ everything-claude-code/
|
|||||||
| |-- swift-actor-persistence/ # Thread-safe Swift data persistence with actors (NEW)
|
| |-- swift-actor-persistence/ # Thread-safe Swift data persistence with actors (NEW)
|
||||||
| |-- swift-protocol-di-testing/ # Protocol-based DI for testable Swift code (NEW)
|
| |-- swift-protocol-di-testing/ # Protocol-based DI for testable Swift code (NEW)
|
||||||
| |-- search-first/ # Research-before-coding workflow (NEW)
|
| |-- search-first/ # Research-before-coding workflow (NEW)
|
||||||
|
| |-- skill-stocktake/ # Audit skills and commands for quality (NEW)
|
||||||
|
| |-- liquid-glass-design/ # iOS 26 Liquid Glass design system (NEW)
|
||||||
|
| |-- foundation-models-on-device/ # Apple on-device LLM with FoundationModels (NEW)
|
||||||
|
| |-- swift-concurrency-6-2/ # Swift 6.2 Approachable Concurrency (NEW)
|
||||||
|
|
|
|
||||||
|-- commands/ # Slash commands for quick execution
|
|-- commands/ # Slash commands for quick execution
|
||||||
| |-- tdd.md # /tdd - Test-driven development
|
| |-- tdd.md # /tdd - Test-driven development
|
||||||
@@ -811,7 +815,7 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|
|||||||
|---------|-------------|----------|--------|
|
|---------|-------------|----------|--------|
|
||||||
| Agents | ✅ 13 agents | ✅ 12 agents | **Claude Code leads** |
|
| Agents | ✅ 13 agents | ✅ 12 agents | **Claude Code leads** |
|
||||||
| Commands | ✅ 32 commands | ✅ 24 commands | **Claude Code leads** |
|
| Commands | ✅ 32 commands | ✅ 24 commands | **Claude Code leads** |
|
||||||
| Skills | ✅ 44 skills | ✅ 16 skills | **Claude Code leads** |
|
| Skills | ✅ 48 skills | ✅ 16 skills | **Claude Code leads** |
|
||||||
| Hooks | ✅ 3 phases | ✅ 20+ events | **OpenCode has more!** |
|
| Hooks | ✅ 3 phases | ✅ 20+ events | **OpenCode has more!** |
|
||||||
| Rules | ✅ 8 rules | ✅ 8 rules | **Full parity** |
|
| Rules | ✅ 8 rules | ✅ 8 rules | **Full parity** |
|
||||||
| MCP Servers | ✅ Full | ✅ Full | **Full parity** |
|
| MCP Servers | ✅ Full | ✅ Full | **Full parity** |
|
||||||
|
|||||||
243
skills/foundation-models-on-device/SKILL.md
Normal file
243
skills/foundation-models-on-device/SKILL.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
---
|
||||||
|
name: foundation-models-on-device
|
||||||
|
description: Apple FoundationModels framework for on-device LLM — text generation, guided generation with @Generable, tool calling, and snapshot streaming in iOS 26+.
|
||||||
|
---
|
||||||
|
|
||||||
|
# FoundationModels: On-Device LLM (iOS 26)
|
||||||
|
|
||||||
|
Patterns for integrating Apple's on-device language model into apps using the FoundationModels framework. Covers text generation, structured output with `@Generable`, custom tool calling, and snapshot streaming — all running on-device for privacy and offline support.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Building AI-powered features using Apple Intelligence on-device
|
||||||
|
- Generating or summarizing text without cloud dependency
|
||||||
|
- Extracting structured data from natural language input
|
||||||
|
- Implementing custom tool calling for domain-specific AI actions
|
||||||
|
- Streaming structured responses for real-time UI updates
|
||||||
|
- Need privacy-preserving AI (no data leaves the device)
|
||||||
|
|
||||||
|
## Core Pattern — Availability Check
|
||||||
|
|
||||||
|
Always check model availability before creating a session:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct GenerativeView: View {
|
||||||
|
private var model = SystemLanguageModel.default
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
switch model.availability {
|
||||||
|
case .available:
|
||||||
|
ContentView()
|
||||||
|
case .unavailable(.deviceNotEligible):
|
||||||
|
Text("Device not eligible for Apple Intelligence")
|
||||||
|
case .unavailable(.appleIntelligenceNotEnabled):
|
||||||
|
Text("Please enable Apple Intelligence in Settings")
|
||||||
|
case .unavailable(.modelNotReady):
|
||||||
|
Text("Model is downloading or not ready")
|
||||||
|
case .unavailable(let other):
|
||||||
|
Text("Model unavailable: \(other)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Pattern — Basic Session
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Single-turn: create a new session each time
|
||||||
|
let session = LanguageModelSession()
|
||||||
|
let response = try await session.respond(to: "What's a good month to visit Paris?")
|
||||||
|
print(response.content)
|
||||||
|
|
||||||
|
// Multi-turn: reuse session for conversation context
|
||||||
|
let session = LanguageModelSession(instructions: """
|
||||||
|
You are a cooking assistant.
|
||||||
|
Provide recipe suggestions based on ingredients.
|
||||||
|
Keep suggestions brief and practical.
|
||||||
|
""")
|
||||||
|
|
||||||
|
let first = try await session.respond(to: "I have chicken and rice")
|
||||||
|
let followUp = try await session.respond(to: "What about a vegetarian option?")
|
||||||
|
```
|
||||||
|
|
||||||
|
Key points for instructions:
|
||||||
|
- Define the model's role ("You are a mentor")
|
||||||
|
- Specify what to do ("Help extract calendar events")
|
||||||
|
- Set style preferences ("Respond as briefly as possible")
|
||||||
|
- Add safety measures ("Respond with 'I can't help with that' for dangerous requests")
|
||||||
|
|
||||||
|
## Core Pattern — Guided Generation with @Generable
|
||||||
|
|
||||||
|
Generate structured Swift types instead of raw strings:
|
||||||
|
|
||||||
|
### 1. Define a Generable Type
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Generable(description: "Basic profile information about a cat")
|
||||||
|
struct CatProfile {
|
||||||
|
var name: String
|
||||||
|
|
||||||
|
@Guide(description: "The age of the cat", .range(0...20))
|
||||||
|
var age: Int
|
||||||
|
|
||||||
|
@Guide(description: "A one sentence profile about the cat's personality")
|
||||||
|
var profile: String
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Request Structured Output
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let response = try await session.respond(
|
||||||
|
to: "Generate a cute rescue cat",
|
||||||
|
generating: CatProfile.self
|
||||||
|
)
|
||||||
|
|
||||||
|
// Access structured fields directly
|
||||||
|
print("Name: \(response.content.name)")
|
||||||
|
print("Age: \(response.content.age)")
|
||||||
|
print("Profile: \(response.content.profile)")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supported @Guide Constraints
|
||||||
|
|
||||||
|
- `.range(0...20)` — numeric range
|
||||||
|
- `.count(3)` — array element count
|
||||||
|
- `description:` — semantic guidance for generation
|
||||||
|
|
||||||
|
## Core Pattern — Tool Calling
|
||||||
|
|
||||||
|
Let the model invoke custom code for domain-specific tasks:
|
||||||
|
|
||||||
|
### 1. Define a Tool
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct RecipeSearchTool: Tool {
|
||||||
|
let name = "recipe_search"
|
||||||
|
let description = "Search for recipes matching a given term and return a list of results."
|
||||||
|
|
||||||
|
@Generable
|
||||||
|
struct Arguments {
|
||||||
|
var searchTerm: String
|
||||||
|
var numberOfResults: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func call(arguments: Arguments) async throws -> ToolOutput {
|
||||||
|
let recipes = await searchRecipes(
|
||||||
|
term: arguments.searchTerm,
|
||||||
|
limit: arguments.numberOfResults
|
||||||
|
)
|
||||||
|
return .string(recipes.map { "- \($0.name): \($0.description)" }.joined(separator: "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Session with Tools
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let session = LanguageModelSession(tools: [RecipeSearchTool()])
|
||||||
|
let response = try await session.respond(to: "Find me some pasta recipes")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Handle Tool Errors
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let answer = try await session.respond(to: "Find a recipe for tomato soup.")
|
||||||
|
} catch let error as LanguageModelSession.ToolCallError {
|
||||||
|
print(error.tool.name)
|
||||||
|
if case .databaseIsEmpty = error.underlyingError as? RecipeSearchToolError {
|
||||||
|
// Handle specific tool error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Pattern — Snapshot Streaming
|
||||||
|
|
||||||
|
Stream structured responses for real-time UI with `PartiallyGenerated` types:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Generable
|
||||||
|
struct TripIdeas {
|
||||||
|
@Guide(description: "Ideas for upcoming trips")
|
||||||
|
var ideas: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
let stream = session.streamResponse(
|
||||||
|
to: "What are some exciting trip ideas?",
|
||||||
|
generating: TripIdeas.self
|
||||||
|
)
|
||||||
|
|
||||||
|
for try await partial in stream {
|
||||||
|
// partial: TripIdeas.PartiallyGenerated (all properties Optional)
|
||||||
|
print(partial)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SwiftUI Integration
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@State private var partialResult: TripIdeas.PartiallyGenerated?
|
||||||
|
@State private var errorMessage: String?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
ForEach(partialResult?.ideas ?? [], id: \.self) { idea in
|
||||||
|
Text(idea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
if let errorMessage { Text(errorMessage).foregroundStyle(.red) }
|
||||||
|
}
|
||||||
|
.task {
|
||||||
|
do {
|
||||||
|
let stream = session.streamResponse(to: prompt, generating: TripIdeas.self)
|
||||||
|
for try await partial in stream {
|
||||||
|
partialResult = partial
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errorMessage = error.localizedDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| On-device execution | Privacy — no data leaves the device; works offline |
|
||||||
|
| 4,096 token limit | On-device model constraint; chunk large data across sessions |
|
||||||
|
| Snapshot streaming (not deltas) | Structured output friendly; each snapshot is a complete partial state |
|
||||||
|
| `@Generable` macro | Compile-time safety for structured generation; auto-generates `PartiallyGenerated` type |
|
||||||
|
| Single request per session | `isResponding` prevents concurrent requests; create multiple sessions if needed |
|
||||||
|
| `response.content` (not `.output`) | Correct API — always access results via `.content` property |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Always check `model.availability`** before creating a session — handle all unavailability cases
|
||||||
|
- **Use `instructions`** to guide model behavior — they take priority over prompts
|
||||||
|
- **Check `isResponding`** before sending a new request — sessions handle one request at a time
|
||||||
|
- **Access `response.content`** for results — not `.output`
|
||||||
|
- **Break large inputs into chunks** — 4,096 token limit applies to instructions + prompt + output combined
|
||||||
|
- **Use `@Generable`** for structured output — stronger guarantees than parsing raw strings
|
||||||
|
- **Use `GenerationOptions(temperature:)`** to tune creativity (higher = more creative)
|
||||||
|
- **Monitor with Instruments** — use Xcode Instruments to profile request performance
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Creating sessions without checking `model.availability` first
|
||||||
|
- Sending inputs exceeding the 4,096 token context window
|
||||||
|
- Attempting concurrent requests on a single session
|
||||||
|
- Using `.output` instead of `.content` to access response data
|
||||||
|
- Parsing raw string responses when `@Generable` structured output would work
|
||||||
|
- Building complex multi-step logic in a single prompt — break into multiple focused prompts
|
||||||
|
- Assuming the model is always available — device eligibility and settings vary
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- On-device text generation for privacy-sensitive apps
|
||||||
|
- Structured data extraction from user input (forms, natural language commands)
|
||||||
|
- AI-assisted features that must work offline
|
||||||
|
- Streaming UI that progressively shows generated content
|
||||||
|
- Domain-specific AI actions via tool calling (search, compute, lookup)
|
||||||
279
skills/liquid-glass-design/SKILL.md
Normal file
279
skills/liquid-glass-design/SKILL.md
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
---
|
||||||
|
name: liquid-glass-design
|
||||||
|
description: iOS 26 Liquid Glass design system — dynamic glass material with blur, reflection, and interactive morphing for SwiftUI, UIKit, and WidgetKit.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Liquid Glass Design System (iOS 26)
|
||||||
|
|
||||||
|
Patterns for implementing Apple's Liquid Glass — a dynamic material that blurs content behind it, reflects color and light from surrounding content, and reacts to touch and pointer interactions. Covers SwiftUI, UIKit, and WidgetKit integration.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Building or updating apps for iOS 26+ with the new design language
|
||||||
|
- Implementing glass-style buttons, cards, toolbars, or containers
|
||||||
|
- Creating morphing transitions between glass elements
|
||||||
|
- Applying Liquid Glass effects to widgets
|
||||||
|
- Migrating existing blur/material effects to the new Liquid Glass API
|
||||||
|
|
||||||
|
## Core Pattern — SwiftUI
|
||||||
|
|
||||||
|
### Basic Glass Effect
|
||||||
|
|
||||||
|
The simplest way to add Liquid Glass to any view:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Text("Hello, World!")
|
||||||
|
.font(.title)
|
||||||
|
.padding()
|
||||||
|
.glassEffect() // Default: regular variant, capsule shape
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customizing Shape and Tint
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Text("Hello, World!")
|
||||||
|
.font(.title)
|
||||||
|
.padding()
|
||||||
|
.glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 16.0))
|
||||||
|
```
|
||||||
|
|
||||||
|
Key customization options:
|
||||||
|
- `.regular` — standard glass effect
|
||||||
|
- `.tint(Color)` — add color tint for prominence
|
||||||
|
- `.interactive()` — react to touch and pointer interactions
|
||||||
|
- Shape: `.capsule` (default), `.rect(cornerRadius:)`, `.circle`
|
||||||
|
|
||||||
|
### Glass Button Styles
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Button("Click Me") { /* action */ }
|
||||||
|
.buttonStyle(.glass)
|
||||||
|
|
||||||
|
Button("Important") { /* action */ }
|
||||||
|
.buttonStyle(.glassProminent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### GlassEffectContainer for Multiple Elements
|
||||||
|
|
||||||
|
Always wrap multiple glass views in a container for performance and morphing:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
GlassEffectContainer(spacing: 40.0) {
|
||||||
|
HStack(spacing: 40.0) {
|
||||||
|
Image(systemName: "scribble.variable")
|
||||||
|
.frame(width: 80.0, height: 80.0)
|
||||||
|
.font(.system(size: 36))
|
||||||
|
.glassEffect()
|
||||||
|
|
||||||
|
Image(systemName: "eraser.fill")
|
||||||
|
.frame(width: 80.0, height: 80.0)
|
||||||
|
.font(.system(size: 36))
|
||||||
|
.glassEffect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `spacing` parameter controls merge distance — closer elements blend their glass shapes together.
|
||||||
|
|
||||||
|
### Uniting Glass Effects
|
||||||
|
|
||||||
|
Combine multiple views into a single glass shape with `glassEffectUnion`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Namespace private var namespace
|
||||||
|
|
||||||
|
GlassEffectContainer(spacing: 20.0) {
|
||||||
|
HStack(spacing: 20.0) {
|
||||||
|
ForEach(symbolSet.indices, id: \.self) { item in
|
||||||
|
Image(systemName: symbolSet[item])
|
||||||
|
.frame(width: 80.0, height: 80.0)
|
||||||
|
.glassEffect()
|
||||||
|
.glassEffectUnion(id: item < 2 ? "group1" : "group2", namespace: namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Morphing Transitions
|
||||||
|
|
||||||
|
Create smooth morphing when glass elements appear/disappear:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@State private var isExpanded = false
|
||||||
|
@Namespace private var namespace
|
||||||
|
|
||||||
|
GlassEffectContainer(spacing: 40.0) {
|
||||||
|
HStack(spacing: 40.0) {
|
||||||
|
Image(systemName: "scribble.variable")
|
||||||
|
.frame(width: 80.0, height: 80.0)
|
||||||
|
.glassEffect()
|
||||||
|
.glassEffectID("pencil", in: namespace)
|
||||||
|
|
||||||
|
if isExpanded {
|
||||||
|
Image(systemName: "eraser.fill")
|
||||||
|
.frame(width: 80.0, height: 80.0)
|
||||||
|
.glassEffect()
|
||||||
|
.glassEffectID("eraser", in: namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Toggle") {
|
||||||
|
withAnimation { isExpanded.toggle() }
|
||||||
|
}
|
||||||
|
.buttonStyle(.glass)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extending Horizontal Scrolling Under Sidebar
|
||||||
|
|
||||||
|
To allow horizontal scroll content to extend under a sidebar or inspector, ensure the `ScrollView` content reaches the leading/trailing edges of the container. The system automatically handles the under-sidebar scrolling behavior when the layout extends to the edges — no additional modifier is needed.
|
||||||
|
|
||||||
|
## Core Pattern — UIKit
|
||||||
|
|
||||||
|
### Basic UIGlassEffect
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let glassEffect = UIGlassEffect()
|
||||||
|
glassEffect.tintColor = UIColor.systemBlue.withAlphaComponent(0.3)
|
||||||
|
glassEffect.isInteractive = true
|
||||||
|
|
||||||
|
let visualEffectView = UIVisualEffectView(effect: glassEffect)
|
||||||
|
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
visualEffectView.layer.cornerRadius = 20
|
||||||
|
visualEffectView.clipsToBounds = true
|
||||||
|
|
||||||
|
view.addSubview(visualEffectView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
visualEffectView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
|
visualEffectView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||||
|
visualEffectView.widthAnchor.constraint(equalToConstant: 200),
|
||||||
|
visualEffectView.heightAnchor.constraint(equalToConstant: 120)
|
||||||
|
])
|
||||||
|
|
||||||
|
// Add content to contentView
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "Liquid Glass"
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
visualEffectView.contentView.addSubview(label)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
label.centerXAnchor.constraint(equalTo: visualEffectView.contentView.centerXAnchor),
|
||||||
|
label.centerYAnchor.constraint(equalTo: visualEffectView.contentView.centerYAnchor)
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
### UIGlassContainerEffect for Multiple Elements
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let containerEffect = UIGlassContainerEffect()
|
||||||
|
containerEffect.spacing = 40.0
|
||||||
|
|
||||||
|
let containerView = UIVisualEffectView(effect: containerEffect)
|
||||||
|
|
||||||
|
let firstGlass = UIVisualEffectView(effect: UIGlassEffect())
|
||||||
|
let secondGlass = UIVisualEffectView(effect: UIGlassEffect())
|
||||||
|
|
||||||
|
containerView.contentView.addSubview(firstGlass)
|
||||||
|
containerView.contentView.addSubview(secondGlass)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scroll Edge Effects
|
||||||
|
|
||||||
|
```swift
|
||||||
|
scrollView.topEdgeEffect.style = .automatic
|
||||||
|
scrollView.bottomEdgeEffect.style = .hard
|
||||||
|
scrollView.leftEdgeEffect.isHidden = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Toolbar Glass Integration
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let favoriteButton = UIBarButtonItem(image: UIImage(systemName: "heart"), style: .plain, target: self, action: #selector(favoriteAction))
|
||||||
|
favoriteButton.hidesSharedBackground = true // Opt out of shared glass background
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Pattern — WidgetKit
|
||||||
|
|
||||||
|
### Rendering Mode Detection
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct MyWidgetView: View {
|
||||||
|
@Environment(\.widgetRenderingMode) var renderingMode
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if renderingMode == .accented {
|
||||||
|
// Tinted mode: white-tinted, themed glass background
|
||||||
|
} else {
|
||||||
|
// Full color mode: standard appearance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accent Groups for Visual Hierarchy
|
||||||
|
|
||||||
|
```swift
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Title")
|
||||||
|
.widgetAccentable() // Accent group
|
||||||
|
Text("Subtitle")
|
||||||
|
// Primary group (default)
|
||||||
|
}
|
||||||
|
Image(systemName: "star.fill")
|
||||||
|
.widgetAccentable() // Accent group
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Rendering in Accented Mode
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Image("myImage")
|
||||||
|
.widgetAccentedRenderingMode(.monochrome)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Background
|
||||||
|
|
||||||
|
```swift
|
||||||
|
VStack { /* content */ }
|
||||||
|
.containerBackground(for: .widget) {
|
||||||
|
Color.blue.opacity(0.2)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| GlassEffectContainer wrapping | Performance optimization, enables morphing between glass elements |
|
||||||
|
| `spacing` parameter | Controls merge distance — fine-tune how close elements must be to blend |
|
||||||
|
| `@Namespace` + `glassEffectID` | Enables smooth morphing transitions on view hierarchy changes |
|
||||||
|
| `interactive()` modifier | Explicit opt-in for touch/pointer reactions — not all glass should respond |
|
||||||
|
| UIGlassContainerEffect in UIKit | Same container pattern as SwiftUI for consistency |
|
||||||
|
| Accented rendering mode in widgets | System applies tinted glass when user selects tinted Home Screen |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Always use GlassEffectContainer** when applying glass to multiple sibling views — it enables morphing and improves rendering performance
|
||||||
|
- **Apply `.glassEffect()` after** other appearance modifiers (frame, font, padding)
|
||||||
|
- **Use `.interactive()`** only on elements that respond to user interaction (buttons, toggleable items)
|
||||||
|
- **Choose spacing carefully** in containers to control when glass effects merge
|
||||||
|
- **Use `withAnimation`** when changing view hierarchies to enable smooth morphing transitions
|
||||||
|
- **Test across appearances** — light mode, dark mode, and accented/tinted modes
|
||||||
|
- **Ensure accessibility contrast** — text on glass must remain readable
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Using multiple standalone `.glassEffect()` views without a GlassEffectContainer
|
||||||
|
- Nesting too many glass effects — degrades performance and visual clarity
|
||||||
|
- Applying glass to every view — reserve for interactive elements, toolbars, and cards
|
||||||
|
- Forgetting `clipsToBounds = true` in UIKit when using corner radii
|
||||||
|
- Ignoring accented rendering mode in widgets — breaks tinted Home Screen appearance
|
||||||
|
- Using opaque backgrounds behind glass — defeats the translucency effect
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Navigation bars, toolbars, and tab bars with the new iOS 26 design
|
||||||
|
- Floating action buttons and card-style containers
|
||||||
|
- Interactive controls that need visual depth and touch feedback
|
||||||
|
- Widgets that should integrate with the system's Liquid Glass appearance
|
||||||
|
- Morphing transitions between related UI states
|
||||||
216
skills/swift-concurrency-6-2/SKILL.md
Normal file
216
skills/swift-concurrency-6-2/SKILL.md
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
---
|
||||||
|
name: swift-concurrency-6-2
|
||||||
|
description: Swift 6.2 Approachable Concurrency — single-threaded by default, @concurrent for explicit background offloading, isolated conformances for main actor types.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Swift 6.2 Approachable Concurrency
|
||||||
|
|
||||||
|
Patterns for adopting Swift 6.2's concurrency model where code runs single-threaded by default and concurrency is introduced explicitly. Eliminates common data-race errors without sacrificing performance.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Migrating Swift 5.x or 6.0/6.1 projects to Swift 6.2
|
||||||
|
- Resolving data-race safety compiler errors
|
||||||
|
- Designing MainActor-based app architecture
|
||||||
|
- Offloading CPU-intensive work to background threads
|
||||||
|
- Implementing protocol conformances on MainActor-isolated types
|
||||||
|
- Enabling Approachable Concurrency build settings in Xcode 26
|
||||||
|
|
||||||
|
## Core Problem: Implicit Background Offloading
|
||||||
|
|
||||||
|
In Swift 6.1 and earlier, async functions could be implicitly offloaded to background threads, causing data-race errors even in seemingly safe code:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Swift 6.1: ERROR
|
||||||
|
@MainActor
|
||||||
|
final class StickerModel {
|
||||||
|
let photoProcessor = PhotoProcessor()
|
||||||
|
|
||||||
|
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
|
||||||
|
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
|
||||||
|
|
||||||
|
// Error: Sending 'self.photoProcessor' risks causing data races
|
||||||
|
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Swift 6.2 fixes this: async functions stay on the calling actor by default.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Swift 6.2: OK — async stays on MainActor, no data race
|
||||||
|
@MainActor
|
||||||
|
final class StickerModel {
|
||||||
|
let photoProcessor = PhotoProcessor()
|
||||||
|
|
||||||
|
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
|
||||||
|
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
|
||||||
|
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Pattern — Isolated Conformances
|
||||||
|
|
||||||
|
MainActor types can now conform to non-isolated protocols safely:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
protocol Exportable {
|
||||||
|
func export()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swift 6.1: ERROR — crosses into main actor-isolated code
|
||||||
|
// Swift 6.2: OK with isolated conformance
|
||||||
|
extension StickerModel: @MainActor Exportable {
|
||||||
|
func export() {
|
||||||
|
photoProcessor.exportAsPNG()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiler ensures the conformance is only used on the main actor:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// OK — ImageExporter is also @MainActor
|
||||||
|
@MainActor
|
||||||
|
struct ImageExporter {
|
||||||
|
var items: [any Exportable]
|
||||||
|
|
||||||
|
mutating func add(_ item: StickerModel) {
|
||||||
|
items.append(item) // Safe: same actor isolation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ERROR — nonisolated context can't use MainActor conformance
|
||||||
|
nonisolated struct ImageExporter {
|
||||||
|
var items: [any Exportable]
|
||||||
|
|
||||||
|
mutating func add(_ item: StickerModel) {
|
||||||
|
items.append(item) // Error: Main actor-isolated conformance cannot be used here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Pattern — Global and Static Variables
|
||||||
|
|
||||||
|
Protect global/static state with MainActor:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Swift 6.1: ERROR — non-Sendable type may have shared mutable state
|
||||||
|
final class StickerLibrary {
|
||||||
|
static let shared: StickerLibrary = .init() // Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix: Annotate with @MainActor
|
||||||
|
@MainActor
|
||||||
|
final class StickerLibrary {
|
||||||
|
static let shared: StickerLibrary = .init() // OK
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MainActor Default Inference Mode
|
||||||
|
|
||||||
|
Swift 6.2 introduces a mode where MainActor is inferred by default — no manual annotations needed:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// With MainActor default inference enabled:
|
||||||
|
final class StickerLibrary {
|
||||||
|
static let shared: StickerLibrary = .init() // Implicitly @MainActor
|
||||||
|
}
|
||||||
|
|
||||||
|
final class StickerModel {
|
||||||
|
let photoProcessor: PhotoProcessor
|
||||||
|
var selection: [PhotosPickerItem] // Implicitly @MainActor
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StickerModel: Exportable { // Implicitly @MainActor conformance
|
||||||
|
func export() {
|
||||||
|
photoProcessor.exportAsPNG()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This mode is opt-in and recommended for apps, scripts, and other executable targets.
|
||||||
|
|
||||||
|
## Core Pattern — @concurrent for Background Work
|
||||||
|
|
||||||
|
When you need actual parallelism, explicitly offload with `@concurrent`:
|
||||||
|
|
||||||
|
> **Important:** This example requires Approachable Concurrency build settings — SE-0466 (MainActor default isolation) and SE-0461 (NonisolatedNonsendingByDefault). With these enabled, `extractSticker` stays on the caller's actor, making mutable state access safe. **Without these settings, this code has a data race** — the compiler will flag it.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
nonisolated final class PhotoProcessor {
|
||||||
|
private var cachedStickers: [String: Sticker] = [:]
|
||||||
|
|
||||||
|
func extractSticker(data: Data, with id: String) async -> Sticker {
|
||||||
|
if let sticker = cachedStickers[id] {
|
||||||
|
return sticker
|
||||||
|
}
|
||||||
|
|
||||||
|
let sticker = await Self.extractSubject(from: data)
|
||||||
|
cachedStickers[id] = sticker
|
||||||
|
return sticker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offload expensive work to concurrent thread pool
|
||||||
|
@concurrent
|
||||||
|
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must await
|
||||||
|
let processor = PhotoProcessor()
|
||||||
|
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
|
||||||
|
```
|
||||||
|
|
||||||
|
To use `@concurrent`:
|
||||||
|
1. Mark the containing type as `nonisolated`
|
||||||
|
2. Add `@concurrent` to the function
|
||||||
|
3. Add `async` if not already asynchronous
|
||||||
|
4. Add `await` at call sites
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| Single-threaded by default | Most natural code is data-race free; concurrency is opt-in |
|
||||||
|
| Async stays on calling actor | Eliminates implicit offloading that caused data-race errors |
|
||||||
|
| Isolated conformances | MainActor types can conform to protocols without unsafe workarounds |
|
||||||
|
| `@concurrent` explicit opt-in | Background execution is a deliberate performance choice, not accidental |
|
||||||
|
| MainActor default inference | Reduces boilerplate `@MainActor` annotations for app targets |
|
||||||
|
| Opt-in adoption | Non-breaking migration path — enable features incrementally |
|
||||||
|
|
||||||
|
## Migration Steps
|
||||||
|
|
||||||
|
1. **Enable in Xcode**: Swift Compiler > Concurrency section in Build Settings
|
||||||
|
2. **Enable in SPM**: Use `SwiftSettings` API in package manifest
|
||||||
|
3. **Use migration tooling**: Automatic code changes via swift.org/migration
|
||||||
|
4. **Start with MainActor defaults**: Enable inference mode for app targets
|
||||||
|
5. **Add `@concurrent` where needed**: Profile first, then offload hot paths
|
||||||
|
6. **Test thoroughly**: Data-race issues become compile-time errors
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Start on MainActor** — write single-threaded code first, optimize later
|
||||||
|
- **Use `@concurrent` only for CPU-intensive work** — image processing, compression, complex computation
|
||||||
|
- **Enable MainActor inference mode** for app targets that are mostly single-threaded
|
||||||
|
- **Profile before offloading** — use Instruments to find actual bottlenecks
|
||||||
|
- **Protect globals with MainActor** — global/static mutable state needs actor isolation
|
||||||
|
- **Use isolated conformances** instead of `nonisolated` workarounds or `@Sendable` wrappers
|
||||||
|
- **Migrate incrementally** — enable features one at a time in build settings
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Applying `@concurrent` to every async function (most don't need background execution)
|
||||||
|
- Using `nonisolated` to suppress compiler errors without understanding isolation
|
||||||
|
- Keeping legacy `DispatchQueue` patterns when actors provide the same safety
|
||||||
|
- Skipping `model.availability` checks in concurrency-related Foundation Models code
|
||||||
|
- Fighting the compiler — if it reports a data race, the code has a real concurrency issue
|
||||||
|
- Assuming all async code runs in the background (Swift 6.2 default: stays on calling actor)
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- All new Swift 6.2+ projects (Approachable Concurrency is the recommended default)
|
||||||
|
- Migrating existing apps from Swift 5.x or 6.0/6.1 concurrency
|
||||||
|
- Resolving data-race safety compiler errors during Xcode 26 adoption
|
||||||
|
- Building MainActor-centric app architectures (most UI apps)
|
||||||
|
- Performance optimization — offloading specific heavy computations to background
|
||||||
Reference in New Issue
Block a user