| name | swift-macos |
| description | Build macOS applications - AppKit, windows, menus, system integration, distribution |
| version | 2.0.0 |
| sasmp_version | 1.3.0 |
| bonded_agent | 05-swift-macos |
| bond_type | PRIMARY_BOND |
Swift macOS Skill
Native macOS application development with AppKit, system integration, and distribution.
Prerequisites
- Xcode 15+ on macOS Sonoma+
- Apple Developer account (for distribution)
- Understanding of AppKit fundamentals
Parameters
parameters:
distribution_method:
type: string
enum: [app_store, developer_id, none]
default: developer_id
sandbox_enabled:
type: boolean
default: true
min_macos_version:
type: string
default: "13.0"
app_type:
type: string
enum: [document_based, single_window, menu_bar, agent]
default: single_window
Topics Covered
AppKit Components
| Component | Purpose |
|---|
| NSWindow | Window management |
| NSViewController | View controller |
| NSView | Base view class |
| NSMenu | Menu bar and context menus |
| NSStatusItem | Menu bar icon |
| NSAlert | Dialog boxes |
Window Types
| Type | Use Case |
|---|
| NSWindow | Standard window |
| NSPanel | Utility/floating window |
| NSPopover | Attached popover |
| NSSavePanel/NSOpenPanel | File dialogs |
Distribution Methods
| Method | Requirements |
|---|
| Mac App Store | Sandbox, App Review |
| Developer ID | Notarization |
| Direct | None (Gatekeeper blocks) |
Code Examples
Menu Bar Application
import AppKit
import SwiftUI
@main
struct MenuBarApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
Settings {
SettingsView()
}
}
}
final class AppDelegate: NSObject, NSApplicationDelegate {
private var statusItem: NSStatusItem!
private var popover: NSPopover!
func applicationDidFinishLaunching(_ notification: Notification) {
setupStatusItem()
setupPopover()
NSApp.setActivationPolicy(.accessory)
}
private func setupStatusItem() {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
if let button = statusItem.button {
button.image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: "App")
button.action = (togglePopover)
}
}
() {
popover ()
popover.contentSize (width: , height: )
popover.behavior .transient
popover.contentViewController (rootView: ())
}
() {
button statusItem.button { }
popover.isShown {
popover.performClose()
} {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
popover.contentViewController.view.window.makeKey()
}
}
}
: {
(\.openSettings) openSettings
body: {
(spacing: ) {
()
.font(.headline)
() {
openSettings()
}
() {
.terminate()
}
}
.padding()
}
}
NSDocument-Based App
import AppKit
final class Document: NSDocument {
var content: String = ""
override class var autosavesInPlace: Bool { true }
override func makeWindowControllers() {
let contentVC = ContentViewController(document: self)
let window = NSWindow(contentViewController: contentVC)
window.setContentSize(NSSize(width: 800, height: 600))
let windowController = NSWindowController(window: window)
addWindowController(windowController)
}
override func data(ofType typeName: String) throws -> Data {
guard let data = content.data(using: .utf8) else {
throw NSError(domain: NSOSStatusErrorDomain, code: writErr)
}
return data
}
override func read(from : , : ) {
content (data: data, encoding: .utf8) {
(domain: , code: readErr)
}
.content content
}
}
: {
document:
(: ) {
.document document
.(nibName: , bundle: )
}
(: ) {
()
}
() {
scrollView ()
textView ()
textView.isEditable
textView.isRichText
textView.font .monospacedSystemFont(ofSize: , weight: .regular)
textView.string document.content
scrollView.documentView textView
scrollView.hasVerticalScroller
view scrollView
}
}
Sandboxing with Security-Scoped Bookmarks
final class SandboxedFileAccess {
private static let bookmarksKey = "securityScopedBookmarks"
static func requestAccess(to url: URL) throws -> URL {
let bookmark = try url.bookmarkData(
options: .withSecurityScope,
includingResourceValuesForKeys: nil,
relativeTo: nil
)
var bookmarks = UserDefaults.standard.dictionary(forKey: bookmarksKey) as? [String: Data] ?? [:]
bookmarks[url.path] = bookmark
UserDefaults.standard.set(bookmarks, forKey: bookmarksKey)
return url
}
static func accessBookmarkedURL(_ path: String) throws -> URL {
guard let bookmarks = UserDefaults.standard.dictionary(forKey: bookmarksKey) as? [String: Data],
let bookmarkData = bookmarks[path] else {
.noBookmark
}
isStale
url (
resolvingBookmarkData: bookmarkData,
options: .withSecurityScope,
relativeTo: ,
bookmarkDataIsStale: isStale
)
isStale {
requestAccess(to: url)
}
url
}
<>( : , : () -> ) -> {
url.startAccessingSecurityScopedResource() {
.accessDenied
}
{ url.stopAccessingSecurityScopedResource() }
perform(url)
}
}
: {
noBookmark
accessDenied
}
Notarization Script
#!/bin/bash
set -e
APP_PATH="$1"
DEVELOPER_ID="Developer ID Application: Your Name (TEAMID)"
KEYCHAIN_PROFILE="notary-profile"
echo "Signing..."
codesign --force --options runtime --sign "$DEVELOPER_ID" \
--deep --strict "$APP_PATH"
echo "Creating ZIP..."
ditto -c -k --keepParent "$APP_PATH" "app.zip"
echo "Submitting for notarization..."
xcrun notarytool submit "app.zip" \
--keychain-profile "$KEYCHAIN_PROFILE" \
--wait
echo "Stapling..."
xcrun stapler staple "$APP_PATH"
echo "Verifying..."
spctl --assess --type execute --verbose=2 "$APP_PATH"
echo "Done!"
rm "app.zip"
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|
| "App is damaged" | Not notarized | Notarize before distribution |
| Sandbox violation | Missing entitlement | Add required entitlement |
| Window not appearing | Wrong activation policy | Check NSApp.activationPolicy |
| Menu not responding | Missing target/action | Verify menu item connections |
| Sparkle updates fail | Code signing issue | Add exception entitlement |
Debug Tips
codesign -dv --verbose=4 App.app
codesign -d --entitlements :- App.app
spctl --assess --type execute App.app
log show --predicate 'subsystem == "com.apple.sandbox"' --last 1h
Validation Rules
validation:
- rule: hardened_runtime
severity: error
check: Enable hardened runtime for notarization
- rule: sandbox_entitlements
severity: warning
check: Only request necessary sandbox entitlements
- rule: code_signing
severity: error
check: Sign with Developer ID for distribution
Usage
Skill("swift-macos")
Related Skills
swift-swiftui - SwiftUI on macOS
swift-architecture - App architecture
swift-testing - Testing macOS apps