opinionated-apple-development/skills/macos-programmer/SKILL.md
macOS-specific development patterns, platform APIs, and decision frameworks. Use when developing Mac apps, macOS applications, Cocoa/AppKit code, or making SwiftUI vs AppKit decisions. Covers NSWindow management, NSDocument architecture, sandboxing, code signing, notarization, and macOS UI patterns. Applies to Mac Catalyst considerations.
npx skillsauth add pyroxin/opinionated-claude-skills macos-programmerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
<skill_scope skill="macos-programmer"> Related skills:
swift-programmer — Swift language fundamentals and Swift 6 concurrencysoftware-engineer — General software engineering principles and system architecturetest-driven-development — Testing philosophy and practicesThis skill covers macOS-specific development patterns, platform APIs, and decision frameworks. It applies when developing Mac apps, working with Cocoa/AppKit code, or making SwiftUI vs AppKit decisions. </skill_scope>
<core_philosophy> Critical Reality (2024-2025): SwiftUI for macOS is powerful but incomplete for production apps. Expert macOS developers must master BOTH SwiftUI and AppKit—SwiftUI for 70% of UI, AppKit for the 30% that makes professional apps professional. This hybrid approach is not a stopgap; it's the current production reality.
Platform Identity: macOS is not iOS with a bigger screen. Multiple windows, menu bars, keyboard navigation, document-based architecture, and precise window management are first-class citizens. Respect macOS conventions; don't port iOS patterns blindly. </core_philosophy>
<swiftui_vs_appkit_decision> The Ground Truth from Production Apps:
SwiftUI maturity varies dramatically by platform. The Ghostty Terminal case study demonstrates this: had to completely rip out SwiftUI's app/window lifecycle (+802/-239 lines) just to implement non-native fullscreen.[^ghostty-devlog] Multi.app's approach: moved to SwiftUI but noted they were "approaching the cusp of dropping macOS 11 support entirely" due to SwiftUI bugs on older versions.[^multi-swiftui]
Use SwiftUI When:
Use AppKit When:
The Hybrid Reality (Recommended for Production):
Multi.app's proven strategy: AppKit for window/app lifecycle, SwiftUI for views where appropriate, bridging via NSHostingController and NSViewRepresentable.
<swiftui_limitations> Key SwiftUI Limitations (as of macOS 15):
Decision Pattern:
Production Mac App
├── AppKit: NSApplication, NSWindow, NSWindowController, NSDocument
├── SwiftUI: View content where appropriate
└── Bridge: NSHostingController, NSViewRepresentable
</swiftui_vs_appkit_decision>
<platform_differences>
Coordinate Systems:
isFlipped to return true for iOS-style coordinatesLayer Backing:
wantsLayer = trueWindows vs Views:
Text System:
let _ = textView.layoutManagerBackground Colors:
drawsBackground propertybackgroundColor like iOSMouse vs Touch:
updateTrackingAreas() for hover effectsNSEvent provides precise cursor position and modifier keys
</platform_differences><window_management>
NSWindow Lifecycle (macOS 10.13+ Change):
Pre-10.13 behavior required releasedWhenClosed = NO to prevent over-release crashes.
macOS 10.13+ SDK: NSWindows that are ordered-in are strongly referenced by AppKit until explicitly ordered-out or closed. Uses CFExecutableLinkedOnOrAfter check—deployment target 10.12 or lower cannot rely on new behavior even on 10.13+.
Window Style and Collection Behavior:
Style masks combine but have limitations:
canBecomeKey → true)Collection behavior controls Spaces/Exposé/fullscreen:
.canJoinAllSpaces: Visible on all spaces (like menu bar).moveToActiveSpace: Follows when space changes.fullScreenPrimary: Can be fullscreen window.fullScreenAuxiliary: Shown with fullscreen window.stationary: Unaffected by Exposé, visible on desktopPattern for always-visible overlay:
window.collectionBehavior = [.canJoinAllSpaces, .stationary]
Multi-Window Document Architecture:
NSDocumentController (singleton)
↓ manages
NSDocument instances (one per document)
↓ manages
NSWindowController instances (one per window)
Modern Document Best Practice:
override class var autosavesInPlace: Bool { true }
Enables automatic version browsing, asynchronous saving, system-managed version storage. </window_management>
<responder_chain>
The Complete Action Message Responder Chain:
Critical Insight: App delegate is NOT part of nextResponder chain—you can never reach it through iteration. It's used as a fallback when current key window's responder chain returns nil.
NSViewController Integration (Yosemite 10.10+):
Pre-10.10: NSViewControllers were NOT in responder chain by default—had to manually patch nextResponder.
Yosemite+: Automatically wires view controllers right after their view, but ONLY if the view controller is added as a child view controller to a parent controller.
Menu Validation Performance:
NSMenu updates EVERY menu item on EVERY user event (mouse move, keypress). This is a performance killer for large menus.
How it works:
validateMenuItem: or validateUserInterfaceItem:, call it and use return valueOptimization:
menu.autoenablesItems = falsemenuItem.isEnabled<swiftui_appkit_integration>
NSHostingController (Essential Bridge Pattern):
// Embedding SwiftUI in AppKit
let swiftUIView = MySwiftUIView()
let hostingController = NSHostingController(rootView: swiftUIView)
// macOS 13+ sizing control
hostingController.sizingOptions = [.intrinsicContentSize]
NSViewRepresentable (Critical Pattern):
The golden rule: ONLY update NSView properties when they've changed. Otherwise you create infinite loops.
struct TextViewRepresentable: NSViewRepresentable {
@Binding var text: String
func updateNSView(_ nsView: NSTextView, context: Context) {
// CRITICAL: Check before setting
if nsView.string != text {
nsView.string = text
}
}
}
State Management with @Observable (macOS 14+):
Critical gotcha: @State with @Observable calls init() on EVERY view rebuild.
Solution: Declare in App struct:
@main
struct MyApp: App {
@State private var appModel = AppModel() // Declare here
var body: some Scene {
WindowGroup {
ContentView().environment(appModel)
}
}
}
Multi-Window Management:
// WindowGroup - Multiple instances
WindowGroup { ContentView() }
// Window - Single unique instance
Window("Stats", id: "stats") { StatsView() }
// UtilityWindow (macOS 14+) - Floating palette
UtilityWindow("Palette", id: "palette") { PaletteView() }
.keyboardShortcut("u")
Menu Bar & Commands:
.commands {
CommandMenu("Custom") {
Button("Action") {}
.keyboardShortcut("x", modifiers: [.command, .shift])
}
}
// Focus values for multi-window menus
@FocusedValue(\.messageState) var messageState
</swiftui_appkit_integration>
Sandboxing Strategy:
Access Methods:
~/Library/Containers/<bundle-id>Security-Scoped Bookmarks (Critical Pattern):
// Save bookmark
let bookmarkData = try url.bookmarkData(options: .withSecurityScope)
// Restore and use
let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope)
guard url.startAccessingSecurityScopedResource() else { throw error }
defer { url.stopAccessingSecurityScopedResource() }
// Access file
Critical Rules:
startAccessingSecurityScopedResource() on resolved URL, not originalEntitlements to Know:
com.apple.security.app-sandbox: Enable App Sandboxcom.apple.security.files.user-selected.read-write: User-selected filescom.apple.security.files.bookmarks.app-scope: App-scoped bookmarkscom.apple.security.network.client: Outgoing network connectionscom.apple.security.network.server: Incoming network connections
</sandboxing>
<code_signing>
Current Process (2024-2025):
codesign --deep --force --options runtime \
--entitlements App.entitlements \
--sign "Developer ID Application: Your Name (TEAMID)" \
App.app
codesign --verify --verbose App.app
ditto -c -k --keepParent App.app App.zip
xcrun notarytool submit App.zip \
--apple-id "[email protected]" \
--team-id "TEAMID" \
--password "@keychain:AC_PASSWORD" \
--wait
xcrun stapler staple App.app
xcrun stapler validate App.app
Critical Changes:
Common Failures:
--options runtime<architecture_patterns>
For general architecture philosophy, see the software-engineer skill. This section covers macOS-specific architectural considerations.
Primary Patterns (Choose One):
What it is:
When to use:
Reality check:
What it is:
When to use:
Reality check:
Pattern:
@Observable
class User {
var name: String = ""
var email: String = ""
func save() {
// Business logic here
}
}
struct ProfileView: View {
@State private var user = User()
var body: some View {
Form {
TextField("Name", text: $user.name)
Button("Save") { user.save() }
}
}
}
What it is:
When to use:
Reality check:
What it is:
When to use:
Reality check:
Complementary Pattern (Add When Needed):
What it is:
When to add Coordinator:
SwiftUI Reality:
Pattern (AppKit/UIKit):
App uses MVC or MVVM for view/logic organization
+
Coordinator manages navigation between screens
Other Concerns:
Patterns like Repository (data access), networking layers, and business logic extraction emerge on a case-by-case basis during actual project design. Don't prematurely abstract. </architecture_patterns>
Profiling with Instruments:
Essential templates:
Best practices:
Main Thread Optimization:
Main thread for UI updates and user input handling ONLY. Move off main thread:
Layer-Backed View Optimization:
Layer-backed views have default redraw policy NSViewLayerContentsRedrawDuringViewResize which is "detrimental to animation performance" as it triggers the drawing step for each frame.[^layer-redraw]
Change to:
view.layerContentsRedrawPolicy = .onSetNeedsDisplay
When to Enable Layer-Backing:
Avoid Layer-Backing When:
Swift Testing vs XCTest (2024-2025):
Swift Testing (Xcode 16+):
@Test, #expect, #require)XCTest:
Recommended Distribution:
Accessibility Testing:
VoiceOver on macOS differs from iOS:
Testing workflow:
<anti_patterns>
Over-Engineering Simple Apps:
Ignoring macOS UI Conventions:
Mac App Store:
Direct Distribution:
Recommendation: Professional tools choose direct distribution for flexibility; consumer apps often choose MAS for trust and distribution. </distribution>
<decision_summary>
SwiftUI vs AppKit:
Architecture Pattern:
State Management:
Cross-Platform:
Local Documentation:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/doc/swift/diagnostics//Applications/Xcode.app/Contents/PlugIns/IDEIntelligenceChat.framework/Versions/A/Resources/AdditionalDocumentation/Apple Official:
Expert Blogs:
Books:
Open Source Examples:
Community:
Embrace Hybrid Solutions: The best Mac apps in 2025 blend SwiftUI and AppKit strategically. Use each framework where it excels. Don't force SwiftUI where AppKit is superior.
Leverage Platform Strengths: macOS isn't iOS with a bigger screen. Multiple windows, menu bars, keyboard navigation, document architecture—these are first-class citizens. iOS developers transitioning to Mac: unlearn iOS assumptions.
Test Relentlessly: Swift Testing for unit tests, XCTest for UI automation. 70% unit, 20% integration, 10% UI. Profile with Instruments regularly. VoiceOver test every release.
Stay Pragmatic: The perfect architecture that ships beats the theoretical ideal that doesn't. Balance theoretical purity with practical delivery. Ship working software, iterate based on real problems, refactor when justified by pain.
Final Wisdom: The best Mac apps feel like Mac apps. They embrace platform conventions, leverage macOS strengths, and don't feel like iPad apps in disguise. Build for Mac users, not iOS users with keyboards. Master the frameworks, respect the platform, ship great software.
[^multi-swiftui]: Multi.app. 2023. Moving to SwiftUI from macOS Cocoa. https://multi.app/blog/moving-to-swiftui-from-macos-cocoa-or-ios-cocoa-touch
[^mv-pattern]: Mohammad Azam. 2022. SwiftUI Architecture — A Complete Guide to the MV Pattern Approach. https://betterprogramming.pub/swiftui-architecture-a-complete-guide-to-mv-pattern-approach-5f411eaaaf9e
[^layer-redraw]: objc.io. AppKit for UIKit Developers. Issue #14. https://www.objc.io/issues/14-mac/appkit-for-uikit-developers </sources>
data-ai
--- name: interactive-research description: Multi-source research orchestrated across a persistent agent team. Use for queries asking for research-style investigation of a topic — comparing, contrasting, surveying, investigating, evaluating, deeply researching, doing a literature review, or any similar research intent — or that ask what people are saying publicly about a topic (discourse, expert opinion, industry consensus, and related framings). Example phrasings (illustrative, not exhaustive):
development
Expert-level guidance for creating high-quality Claude Code skills. Use alongside Anthropic's skill-creator when creating new skills, improving existing skills, or needing guidance on skill content quality. Complements basic skill mechanics with research-driven content development, XML tag structuring, decision frameworks over mechanics, cross-references between skills, and systematic validation.
development
--- name: deep-research description: Deprecated name; the skill formerly here is now opinionated-research:interactive-research. Retained as a context-level notice because Claude Code now ships a built-in deep-research workflow that runs one-shot, non-interactive fan-out research (no conversation after the report). interactive-research instead runs a persistent agent team you can keep questioning once the report lands. Guidance for Claude: when the user makes a research request, ask whether they
development
Pedagogical framework for teaching programming through Socratic dialogue. Use when a learner wants to LEARN programming rather than have code written for them. Triggers include "teach me", "help me understand", "I'm learning", "tutor mode", or requests to not provide solutions. Emphasizes productive struggle, graduated hints, metacognitive scaffolding, and emotional support.