Come usare
- Copia il comando e controlla il pacchetto prima di installarlo nel tuo ambiente agente.
- Controlla strumenti, credenziali e ambienti richiesti nel documento originale.
- Descrivi input e risultato desiderato; confronta l’output con le fonti.
npx skills add https://github.com/daveebbelaar/bama --skill swift-swiftuiPrima di iniziare
- L’installazione non include abbonamenti, quote API o accesso ai file.
- Stelle e installazioni indicano attività, non un test indipendente di qualità o sicurezza.
Contenuto dal repository con titoli, esempi, codice, tabelle, link e immagini preservati.
Swift and SwiftUI
The code is right when it compiles with zero warnings in Swift 6 language mode and the app does what the story says. For how it should look, call the Skill tool with mac-design. For copyable view code, read ../../design/mac-design/patterns.md.
Concurrency
The template sets main-actor default isolation: every type and function runs on the main actor unless you say otherwise. UI code needs no annotations.
| Work | Put it in |
|---|---|
| UI state, views, view models | Default (main actor) |
| CPU-heavy pure work (parsing, image work, diffing) | A nonisolated function marked @concurrent |
| Shared mutable state used off the main thread | An actor |
| A C callback (event tap, Carbon hotkey, CoreAudio listener) | A nonisolated static func that hops back with Task { @MainActor in ... } |
- Data that crosses actors is
Sendable: structs and enums ofSendable
values, or final class with only let properties.
MainActor.assumeIsolated { }inside a callback you know runs on the main
thread (a main run loop Timer, a main queue notification).
Task { }inherits the actor.Task.detachedonly when it must not.- Fix a concurrency error at its type, not with
@unchecked Sendableor
nonisolated(unsafe). Those two are a last resort, with a comment why.
- Runtime trap the compiler misses. A closure created inside a
@MainActor type and passed to an API without a @Sendable closure parameter (DispatchSource.setEventHandler, DispatchWorkItem, addPeriodicTimeObserver, CoreAudio listener blocks) inherits the main actor. When it runs on a background queue, Swift 6 crashes the app. Write it as { @Sendable in ... }, and run every background path once after any concurrency change.
State
@Observableclasses for models.@Statein the view that owns one,
@Bindable to bind to its properties, .environment(model) to share it.
@ObservationIgnoredon caches, timers, and handles, so they never
trigger a redraw.
@Statefor view-local values.@AppStoragefor small preferences.
Keychain for secrets.
ForEach(items)over values, neverForEach($items)for data the user
can delete: a row can read an index that no longer exists and crash. Get a binding by looking the item up by id.
SwiftData
@Modelclasses,.modelContaineron the scene,@Queryin views,
@Environment(\.modelContext) to insert and delete.
- Every new property has a default value, so existing stores still open.
- Store at
~/Library/Application Support/<AppName>/. Allow an override
(APP_STORE) so tests never touch the real data.
- Give every model its own
uid: UUID.persistentModelIDchanges on the
first save, so it breaks selection and lookups.
- Undo: register the reverse of each action on the window's undo manager.
SwiftData's automatic undo is not reliable. Code in ../../design/mac-design/patterns.md (Undo with SwiftData).
- Need SQL, a CLI, or several processes on one file? Use SQLite with WAL
through a small library target instead.
Menu commands
SwiftUI Commands do not observe @Observable models, so a title or a .disabled(...) that reads the model goes stale. In testing, neither @FocusedValue (from .focusedSceneValue) nor an @ObservedObject inside the commands updated reliably: the menu stayed disabled with a row selected. What works: mirror the selection into a small ObservableObject, observe it in the App, and pass the values into the commands. The App's body then rebuilds the menus when they change.
@MainActor final class MenuState: ObservableObject {
static let shared = MenuState()
@Published private(set) var selection: Set<UUID> = []
func update(_ s: Set<UUID>) { if selection != s { selection = s } }
}
// AppModel: var selection: Set<UUID> = [] { didSet { MenuState.shared.update(selection) } }
struct MyApp: App {
@ObservedObject private var menu = MenuState.shared
var body: some Scene {
Window("App", id: "main") { ContentView() }
.commands { AppCommands(model: model, selectedIDs: menu.selection) }
}
}Disable items with .disabled(selectedIDs.isEmpty) and resolve the target again when the item fires. Never ship items that stay enabled and do nothing. Check the menu from outside the app (test-mac-app skill, "Menu check").
A menu shortcut also fires while a text field has focus. Never use Delete, Command-Delete, arrows, or Return as menu shortcuts. Handle them on the list with .onDeleteCommand and .onKeyPress.
Names Swift already uses
Never name a type Task, View, Color, Image, Text, Section, Label, Menu, Group, or Settings. Task breaks every Task { }. Use Todo, TaskItem, NoteSection.
AppKit bridges
SwiftUI covers windows, lists, and forms. AppKit is still the answer for:
- Floating panels that do not steal focus:
NSPanelwith
.nonactivatingPanel hosting a SwiftUI view (NSHostingView, with sizingOptions = [] for a fixed frame).
- Text views with custom behavior:
NSViewRepresentablearoundNSTextView. - Status items with custom click handling:
NSStatusItem. - Key and mouse monitors:
NSEvent.addLocalMonitorForEvents. Remove the
monitor when the view goes away.
For system hooks (hotkeys, Accessibility, screen capture), call the Skill tool with os-integrations.
Compiler errors
| Error | Fix |
|---|---|
cannot find 'X' in scope right after adding a file | swift package clean --scratch-path ~/Library/Developer/<AppName>/build |
Main actor-isolated ... from a nonisolated context | Make the caller main-actor, or await the call |
Sending 'x' risks causing data races | Make the type Sendable, or keep it on one actor |
The compiler is unable to type-check this expression | Split the view into smaller subviews or let constants |
ambiguous use of 'Task' | A type in your code is named Task; rename it |
Fix the first error first. Later errors often come from it.
Swift 6 errors
Moving an existing Swift 5 app? Read ../../ship/build-and-verify/existing-app.md first: expect several build rounds, then hunt the runtime trap above.
| Error | Fix |
|---|---|
Static let of a non-Sendable type (formatter, NSFont, CFString) | @MainActor on the enclosing type, a computed var, or a commented nonisolated(unsafe) |
sending 'note' risks causing data races in a main-queue observer | Copy the Sendable parts (an id, a pid) out of the notification before MainActor.assumeIsolated |
| Observer token captured before it is assigned | Keep the token in a small @MainActor class |
| "pattern that the region-based isolation checker does not understand" in a task group | Use an array of Task { @MainActor in ... } and await each one |
Vars changed from OperationQueue blocks under NSLock | OSAllocatedUnfairLock(initialState:) |
@objc action on an NSMenuItem subclass | @MainActor @objc func |
Done when the app builds with zero warnings in Swift 6 language mode and the story's acceptance checks pass.

