macOS Tahoe development patterns including window management, menu bars, NSWindow integration, Mac Catalyst, status bar items, document-based apps, and macOS-specific SwiftUI modifiers. Use when user asks about macOS development, window management, menu bar, status items, Mac Catalyst, document apps, or macOS-specific patterns.
/plugin marketplace add bluewaves-creations/bluewaves-skills/plugin install swift-apple-dev@bluewaves-skillsThis skill is limited to using the following tools:
Comprehensive guide to macOS Tahoe development, window management, menu bars, document-based apps, and macOS-specific SwiftUI patterns.
import SwiftUI
@main
struct MyMacApp: App {
var body: some Scene {
// Standard window
WindowGroup {
ContentView()
}
.windowStyle(.automatic) // Default
// Hideable title bar (content extends to top)
WindowGroup("Editor", id: "editor") {
EditorView()
}
.windowStyle(.hiddenTitleBar)
// Plain window (no chrome)
WindowGroup("Floating", id: "floating") {
FloatingView()
}
.windowStyle(.plain)
}
}
WindowGroup {
ContentView()
}
// Default size
.defaultSize(width: 800, height: 600)
// Size constraints
.windowResizability(.contentSize) // Fit content
.windowResizability(.contentMinSize) // Min = content, resizable larger
.windowResizability(.automatic) // System decides
// Fixed size window
.windowResizability(.contentSize)
.frame(width: 400, height: 300)
// Position
.defaultPosition(.center)
.defaultPosition(.topLeading)
.defaultPosition(UnitPoint(x: 0.75, y: 0.25))
@main
struct MultiWindowApp: App {
@Environment(\.openWindow) private var openWindow
var body: some Scene {
// Main window
WindowGroup {
MainView()
.toolbar {
Button("New Editor") {
openWindow(id: "editor")
}
}
}
.commands {
CommandGroup(after: .newItem) {
Button("New Editor Window") {
openWindow(id: "editor")
}
.keyboardShortcut("e", modifiers: [.command, .shift])
}
}
// Secondary window type
WindowGroup("Editor", id: "editor") {
EditorView()
}
.defaultSize(width: 600, height: 400)
// Single instance window
Window("Settings", id: "settings") {
SettingsView()
}
.keyboardShortcut(",", modifiers: .command)
.defaultSize(width: 500, height: 400)
}
}
// Define window value type
struct DocumentInfo: Codable, Hashable {
let id: UUID
let title: String
}
@main
struct DocumentApp: App {
var body: some Scene {
WindowGroup(for: DocumentInfo.self) { $document in
if let document {
DocumentView(info: document)
}
}
}
}
// Open with specific data
struct ContentView: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Open Document") {
openWindow(value: DocumentInfo(id: UUID(), title: "New Doc"))
}
}
}
import AppKit
import SwiftUI
struct WindowAccessor: NSViewRepresentable {
let callback: (NSWindow?) -> Void
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
callback(view.window)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
// Usage
struct ContentView: View {
@State private var window: NSWindow?
var body: some View {
Text("Hello")
.background(WindowAccessor { window in
self.window = window
// Customize window
window?.titlebarAppearsTransparent = true
window?.titleVisibility = .hidden
window?.styleMask.insert(.fullSizeContentView)
window?.isMovableByWindowBackground = true
})
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
// Replace existing menu group
CommandGroup(replacing: .newItem) {
Button("New Document") {
// Create new document
}
.keyboardShortcut("n")
Button("New from Template...") {
// Show template picker
}
.keyboardShortcut("n", modifiers: [.command, .shift])
}
// Add to existing menu
CommandGroup(after: .sidebar) {
Divider()
Button("Toggle Inspector") {
// Toggle inspector
}
.keyboardShortcut("i", modifiers: [.command, .option])
}
// Custom menu
CommandMenu("Canvas") {
Button("Zoom In") { }
.keyboardShortcut("+")
Button("Zoom Out") { }
.keyboardShortcut("-")
Divider()
Button("Fit to Window") { }
.keyboardShortcut("0")
}
}
}
}
struct ItemView: View {
let item: Item
@State private var isRenaming = false
var body: some View {
Text(item.title)
.contextMenu {
Button("Open") {
// Open action
}
Button("Open in New Window") {
// Open in new window
}
Divider()
Button("Rename") {
isRenaming = true
}
Button("Duplicate") {
// Duplicate action
}
Divider()
Button("Delete", role: .destructive) {
// Delete action
}
}
}
}
@main
struct StatusBarApp: App {
var body: some Scene {
// Optional main window
WindowGroup {
ContentView()
}
// Status bar item
MenuBarExtra("My App", systemImage: "star.fill") {
Button("Show Dashboard") {
// Open main window
}
.keyboardShortcut("d")
Divider()
Menu("Recent Items") {
ForEach(recentItems) { item in
Button(item.name) {
// Open item
}
}
}
Divider()
Button("Preferences...") {
// Open preferences
}
.keyboardShortcut(",")
Button("Quit") {
NSApplication.shared.terminate(nil)
}
.keyboardShortcut("q")
}
}
@State private var recentItems: [RecentItem] = []
}
// Custom status bar view
struct StatusBarApp2: App {
var body: some Scene {
MenuBarExtra {
StatusBarPopover()
} label: {
HStack(spacing: 4) {
Image(systemName: "cpu")
Text("45%")
.font(.caption)
}
}
.menuBarExtraStyle(.window) // Popover style
}
}
struct StatusBarPopover: View {
var body: some View {
VStack(spacing: 16) {
Text("System Status")
.font(.headline)
// Status content
StatusRow(title: "CPU", value: "45%")
StatusRow(title: "Memory", value: "8.2 GB")
StatusRow(title: "Storage", value: "234 GB")
Divider()
Button("Open Activity Monitor") {
// Open Activity Monitor
}
}
.padding()
.frame(width: 200)
}
}
import SwiftUI
import UniformTypeIdentifiers
// Define document type
extension UTType {
static var myDocument: UTType {
UTType(exportedAs: "com.mycompany.mydocument")
}
}
// Document model
struct MyDocument: FileDocument {
static var readableContentTypes: [UTType] { [.myDocument, .plainText] }
static var writableContentTypes: [UTType] { [.myDocument] }
var content: String
init(content: String = "") {
self.content = content
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8) else {
throw CocoaError(.fileReadCorruptFile)
}
content = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = content.data(using: .utf8)!
return FileWrapper(regularFileWithContents: data)
}
}
// Document-based app
@main
struct DocumentApp: App {
var body: some Scene {
DocumentGroup(newDocument: MyDocument()) { file in
DocumentView(document: file.$document)
}
.commands {
// Document-specific commands
CommandGroup(after: .saveItem) {
Button("Export as PDF...") {
// Export
}
.keyboardShortcut("e", modifiers: [.command, .shift])
}
}
}
}
struct DocumentView: View {
@Binding var document: MyDocument
@FocusedValue(\.document) private var focusedDocument
var body: some View {
TextEditor(text: $document.content)
.font(.body.monospaced())
.focusedValue(\.document, $document)
}
}
import SwiftUI
import UniformTypeIdentifiers
// For large files, use ReferenceFileDocument
@Observable
class ImageDocument: ReferenceFileDocument {
static var readableContentTypes: [UTType] { [.png, .jpeg] }
static var writableContentTypes: [UTType] { [.png] }
var image: NSImage?
var annotations: [Annotation] = []
init() {}
required init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents else {
throw CocoaError(.fileReadCorruptFile)
}
image = NSImage(data: data)
}
func snapshot(contentType: UTType) throws -> Data {
guard let image, let data = image.tiffRepresentation else {
throw CocoaError(.fileWriteUnknown)
}
return data
}
func fileWrapper(snapshot: Data, configuration: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: snapshot)
}
}
@main
struct ImageEditorApp: App {
var body: some Scene {
DocumentGroup(newDocument: { ImageDocument() }) { file in
ImageEditorView(document: file.document)
}
}
}
In Xcode: Target → General → Deployment Info → Mac (Mac Catalyst)
struct ContentView: View {
var body: some View {
VStack {
#if targetEnvironment(macCatalyst)
// Mac Catalyst specific UI
MacToolbar()
#else
// iOS specific UI
iOSToolbar()
#endif
MainContent()
}
}
}
// Check at runtime
struct PlatformAwareView: View {
var body: some View {
Group {
if ProcessInfo.processInfo.isMacCatalystApp {
MacLayout()
} else {
iOSLayout()
}
}
}
}
#if targetEnvironment(macCatalyst)
import AppKit
extension View {
func configureMacWindow() -> some View {
self.onAppear {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
// Window size
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 800, height: 600)
windowScene.sizeRestrictions?.maximumSize = CGSize(width: 1920, height: 1080)
// Title bar
if let titlebar = windowScene.titlebar {
titlebar.titleVisibility = .hidden
titlebar.toolbar = nil
}
}
}
}
#endif
struct CatalystOptimizedApp: App {
var body: some Scene {
WindowGroup {
ContentView()
#if targetEnvironment(macCatalyst)
.frame(minWidth: 800, minHeight: 600)
.onAppear(perform: setupMacEnvironment)
#endif
}
#if targetEnvironment(macCatalyst)
.commands {
// Mac-specific menu commands
CommandGroup(replacing: .help) {
Button("MyApp Help") {
// Open help
}
}
}
#endif
}
#if targetEnvironment(macCatalyst)
private func setupMacEnvironment() {
// Enable hover effects
// Configure pointer interactions
// Set up keyboard shortcuts
}
#endif
}
// Pointer/hover support
struct HoverButton: View {
@State private var isHovered = false
var body: some View {
Button("Click Me") { }
.buttonStyle(.borderedProminent)
.scaleEffect(isHovered ? 1.05 : 1.0)
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.15)) {
isHovered = hovering
}
}
}
}
struct MacToolbarView: View {
@State private var searchText = ""
var body: some View {
NavigationSplitView {
Sidebar()
} detail: {
DetailView()
}
.toolbar {
// Leading items
ToolbarItem(placement: .navigation) {
Button(action: {}) {
Image(systemName: "sidebar.left")
}
}
// Principal (center)
ToolbarItem(placement: .principal) {
Picker("View", selection: .constant(0)) {
Text("Grid").tag(0)
Text("List").tag(1)
}
.pickerStyle(.segmented)
.frame(width: 150)
}
// Trailing items
ToolbarItemGroup(placement: .primaryAction) {
Button(action: {}) {
Image(systemName: "plus")
}
Button(action: {}) {
Image(systemName: "square.and.arrow.up")
}
}
// Search field (trailing)
ToolbarItem(placement: .automatic) {
TextField("Search", text: $searchText)
.textFieldStyle(.roundedBorder)
.frame(width: 200)
}
}
.toolbarBackground(.visible, for: .windowToolbar)
}
}
struct ThreeColumnLayout: View {
@State private var selectedFolder: Folder?
@State private var selectedItem: Item?
@State private var columnVisibility: NavigationSplitViewVisibility = .all
var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
// Sidebar (first column)
List(folders, selection: $selectedFolder) { folder in
NavigationLink(value: folder) {
Label(folder.name, systemImage: folder.icon)
}
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200, max: 250)
} content: {
// Content (second column)
if let folder = selectedFolder {
List(folder.items, selection: $selectedItem) { item in
NavigationLink(value: item) {
ItemRow(item: item)
}
}
} else {
ContentUnavailableView("Select a Folder", systemImage: "folder")
}
} detail: {
// Detail (third column)
if let item = selectedItem {
ItemDetailView(item: item)
} else {
ContentUnavailableView("Select an Item", systemImage: "doc")
}
}
.navigationSplitViewStyle(.balanced)
}
@State private var folders: [Folder] = []
}
@main
struct PreferencesApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
#if os(macOS)
Settings {
SettingsView()
}
#endif
}
}
struct SettingsView: View {
var body: some View {
TabView {
GeneralSettings()
.tabItem {
Label("General", systemImage: "gear")
}
AppearanceSettings()
.tabItem {
Label("Appearance", systemImage: "paintbrush")
}
AccountSettings()
.tabItem {
Label("Account", systemImage: "person.crop.circle")
}
AdvancedSettings()
.tabItem {
Label("Advanced", systemImage: "gearshape.2")
}
}
.frame(width: 450, height: 300)
}
}
struct GeneralSettings: View {
@AppStorage("launchAtLogin") private var launchAtLogin = false
@AppStorage("checkForUpdates") private var checkForUpdates = true
var body: some View {
Form {
Toggle("Launch at Login", isOn: $launchAtLogin)
Toggle("Check for Updates Automatically", isOn: $checkForUpdates)
}
.padding()
}
}
struct InspectorView: View {
@State private var showInspector = true
@State private var selectedItem: Item?
var body: some View {
NavigationStack {
ContentList(selection: $selectedItem)
}
.inspector(isPresented: $showInspector) {
if let item = selectedItem {
ItemInspector(item: item)
} else {
ContentUnavailableView("No Selection", systemImage: "sidebar.right")
}
}
.inspectorColumnWidth(min: 250, ideal: 300, max: 400)
.toolbar {
ToolbarItem {
Button {
showInspector.toggle()
} label: {
Image(systemName: "sidebar.right")
}
}
}
}
}
struct KeyboardShortcutDemo: View {
@State private var text = ""
var body: some View {
TextEditor(text: $text)
.toolbar {
// Toolbar button with shortcut
Button("Bold") {
makeBold()
}
.keyboardShortcut("b", modifiers: .command)
Button("Italic") {
makeItalic()
}
.keyboardShortcut("i", modifiers: .command)
}
// Background keyboard shortcuts
.background {
Group {
Button("") { duplicateLine() }
.keyboardShortcut("d", modifiers: [.command, .shift])
Button("") { deleteLine() }
.keyboardShortcut(.delete, modifiers: [.command, .shift])
}
.frame(width: 0, height: 0)
.opacity(0)
}
}
private func makeBold() {}
private func makeItalic() {}
private func duplicateLine() {}
private func deleteLine() {}
}
struct FocusedDocument: FocusedValueKey {
typealias Value = Binding<MyDocument>
}
extension FocusedValues {
var document: Binding<MyDocument>? {
get { self[FocusedDocument.self] }
set { self[FocusedDocument.self] = newValue }
}
}
struct DocumentCommands: Commands {
@FocusedValue(\.document) var document
var body: some Commands {
CommandGroup(after: .textEditing) {
Button("Word Count") {
if let doc = document?.wrappedValue {
let count = doc.content.split(separator: " ").count
print("Words: \(count)")
}
}
.keyboardShortcut("w", modifiers: [.command, .shift])
.disabled(document == nil)
}
}
}
// ✓ Use native macOS controls
NavigationSplitView { ... }
// ✓ Support keyboard navigation
.focusable()
.onKeyPress(.tab) { ... }
// ✓ Respect system appearance
@Environment(\.colorScheme) var colorScheme
// ✓ Use Settings scene for preferences
Settings { SettingsView() }
// ✓ Support window restoration
.handlesExternalEvents(matching: ["main"])
// ✗ Don't use iOS-only patterns
.navigationBarHidden(true) // Use .toolbar instead
// ✗ Don't ignore keyboard shortcuts
// Always add for common actions
// ✗ Don't hardcode window sizes
.frame(width: 800, height: 600) // Use defaultSize instead
// ✗ Don't forget right-click menus
// Add contextMenu to interactive elements
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.