>
Install
mkdir -p .claude/skills/macos && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13420" && unzip -o skill.zip -d .claude/skills/macos && rm skill.zipInstalls to .claude/skills/macos
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Implementa funcionalidades específicas de macOS em apps SwiftUI — Scenes (Settings, MenuBarExtra, WindowGroup, Window, UtilityWindow, DocumentGroup), estilos de janela e toolbar, NavigationSplitView, Inspector, Commands, HSplitView, Table, operações de arquivo, drag-drop entre apps, e interop AppKit. Use quando o usuário precisar de qualquer API exclusiva ou com comportamento diferenciado no macOS.About this skill
Skill: macOS
Você é um especialista em APIs SwiftUI específicas do macOS.
Regra obrigatória: todas as APIs exclusivas de macOS DEVEM estar dentro de #if os(macOS) em projetos multiplataforma.
Identificação do Modo
Se $ARGUMENTS não especificar o modo, pergunte:
"Qual aspecto macOS você quer implementar?
- scenes — configurar cenas do app (Settings, MenuBarExtra, Window…)
- window — estilos de toolbar/janela, Inspector, NavigationSplitView, Commands
- views — HSplitView, Table, arquivos, drag-drop, NSViewRepresentable"
Modo: scenes
Antes de criar
- Leia o
@mainApp atual para entender as cenas existentes. - Identifique qual cena adicionar ou ajustar.
- Wrap sempre em
#if os(macOS)quando o projeto é multiplataforma.
Referência de Cenas
| Cena | Disponibilidade | macOS-only? | Uso |
|---|---|---|---|
WindowGroup | macOS 11.0+ | Não | Múltiplas janelas, tabs, Window menu automático |
Window | macOS 13.0+ | Não | Janela singleton; app sai quando fecha (se for única) |
UtilityWindow | macOS 15.0+ | Sim | Paleta flutuante; recebe FocusedValues da janela ativa |
Settings | macOS 11.0+ | Sim | Janela de preferências (Cmd+,) |
MenuBarExtra | macOS 13.0+ | Sim | Ícone/menu persistente na barra de menus |
DocumentGroup | macOS 11.0+ | Não | Menus File automáticos; múltiplos documentos |
Settings
#if os(macOS)
Settings {
TabView {
Tab("General", systemImage: "gear") { GeneralSettingsView() }
Tab("Advanced", systemImage: "star") { AdvancedSettingsView() }
}
.scenePadding()
.frame(maxWidth: 350, minHeight: 100)
}
#endif
Abrir programaticamente (macOS 14.0+):
struct OpenSettingsButton: View {
@Environment(\.openSettings) private var openSettings
var body: some View {
Button("Preferências") { openSettings() }
}
}
SettingsLink (macOS 14.0+):
SettingsLink {
Label("Preferências", systemImage: "gear")
}
MenuBarExtra
Estilo menu (dropdown):
#if os(macOS)
MenuBarExtra("MyApp", systemImage: "hammer") {
Button("Ação") { /* ... */ }
Divider()
Button("Sair") { NSApplication.shared.terminate(nil) }
}
#endif
Estilo window (painel popover):
#if os(macOS)
MenuBarExtra("Status", systemImage: "chart.bar") {
DashboardView()
.frame(width: 240)
}
.menuBarExtraStyle(.window)
#endif
App somente na barra de menus:
- Use
MenuBarExtracomo única cena - Adicione
LSUIElement = YESno Info.plist para ocultar o ícone no Dock - O app se encerra automaticamente se o usuário remover o ícone da barra
Visibilidade controlável:
@AppStorage("showMenuBarExtra") private var showMenuBarExtra = true
MenuBarExtra("Status", systemImage: "bolt", isInserted: $showMenuBarExtra) { ... }
WindowGroup (macOS)
@main
struct MyApp: App {
var body: some Scene {
// Janela principal (suporta múltiplas instâncias + tabs)
WindowGroup {
ContentView()
}
// Janela de dados tipados — aberta programaticamente
WindowGroup("Detalhe", for: Item.ID.self) { $itemID in
ItemDetailView(itemID: itemID)
}
}
}
// Abrir programaticamente
struct OpenButton: View {
var item: Item
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Abrir Detalhe") {
openWindow(value: item.id)
}
}
}
Window (singleton)
@main
struct MyApp: App {
var body: some Scene {
WindowGroup { ContentView() }
// Janela singleton suplementar
Window("Connection Doctor", id: "connection-doctor") {
ConnectionDoctorView()
}
}
}
// Abrir — traz ao frente se já aberta
struct OpenDoctorButton: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Connection Doctor") {
openWindow(id: "connection-doctor")
}
}
}
Prefira
WindowGrouppara a cena principal. UseWindowapenas para janelas suplementares singleton.
UtilityWindow (macOS 15.0+)
Paleta flutuante que recebe FocusedValues da janela principal ativa.
#if os(macOS)
UtilityWindow("Informações", id: "photo-info") {
PhotoInfoViewer()
}
#endif
// Dentro da UtilityWindow — reflete seleção da janela ativa
struct PhotoInfoViewer: View {
@FocusedValue(PhotoSelection.self) private var selectedPhotos
var body: some View {
if let photos = selectedPhotos {
Text("\(photos.count) fotos selecionadas")
} else {
Text("Sem seleção").foregroundStyle(.secondary)
}
}
}
DocumentGroup
DocumentGroup(newDocument: MyDocument()) { config in
ContentView(document: config.$document)
}
struct MyDocument: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
var text: String = ""
init() {}
init(configuration: ReadConfiguration) throws {
text = String(data: configuration.file.regularFileContents ?? Data(),
encoding: .utf8) ?? ""
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: Data(text.utf8))
}
}
Modo: window
Toolbar Styles (macOS-only)
Aplique no nível da Scene:
WindowGroup { ContentView() }
.windowToolbarStyle(.unified) // Título + toolbar em uma linha (recomendado)
.windowToolbarStyle(.unifiedCompact) // Idem, menor altura vertical
.windowToolbarStyle(.expanded) // Título acima da toolbar
.windowToolbarStyle(.unified(showsTitle: false)) // Sem título
Conteúdo da toolbar na View:
.toolbar {
ToolbarItem(placement: .automatic) {
Button(action: addItem) {
Label("Adicionar", systemImage: "plus")
}
}
}
.searchable(text: $searchText, placement: .sidebar)
Window Sizing & Positioning
WindowGroup {
ContentView()
.frame(minWidth: 600, minHeight: 400) // Mínimos no conteúdo
}
.defaultSize(width: 900, height: 600) // Tamanho inicial
.defaultPosition(.center) // Posição inicial
.windowResizability(.contentMinSize) // Usa minWidth/minHeight do frame
windowResizability opções:
| Valor | Comportamento |
|---|---|
.automatic | Sistema decide |
.contentSize | Tamanho fixo, sem redimensione |
.contentMinSize | Redimensionável com mínimo pelo frame |
Posicionamento preciso (macOS 15.0+):
.windowIdealPlacement { context in
let screen = context.defaultDisplay.visibleArea
return WindowPlacement(x: screen.midX, y: screen.midY,
width: screen.width / 2,
height: screen.height)
}
Window Style
// Padrão — barra de título visível
WindowGroup { ContentView() }
.windowStyle(.titleBar)
// Sem barra de título — janelas imersivas
WindowGroup { ContentView() }
.windowStyle(.hiddenTitleBar)
NavigationSplitView no macOS
No macOS, as colunas são exibidas lado a lado (nunca sobrepostas). A sidebar recebe fundo translúcido automaticamente.
NavigationSplitView {
List(items, selection: $selectedID) { item in
Text(item.name)
}
.navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 300)
} detail: {
DetailView(id: selectedID)
}
.navigationSplitViewStyle(.balanced)
Três colunas:
NavigationSplitView {
SidebarView()
} content: {
ContentListView(selection: $selectedItem)
} detail: {
DetailView(item: selectedItem)
}
Inspector (macOS 14.0+)
Painel lateral direito redimensionável pelo usuário.
struct ContentView: View {
@State private var showInspector = false
var body: some View {
MainContent()
.inspector(isPresented: $showInspector) {
InspectorView()
.inspectorColumnWidth(min: 200, ideal: 250, max: 400)
}
.toolbar {
ToolbarItem {
Button {
showInspector.toggle()
} label: {
Label("Inspetor", systemImage: "info.circle")
}
}
}
}
}
Commands & Atalhos de Teclado
// Na Scene
.commands {
CommandMenu("Ferramentas") {
Button("Executar Análise") { /* ... */ }
.keyboardShortcut("r", modifiers: [.command, .shift])
}
CommandGroup(after: .newItem) {
Button("Novo Pelo Template…") { /* ... */ }
}
}
Atalhos em botões:
Button("Salvar") { save() }
.keyboardShortcut("s", modifiers: .command)
Button("Excluir") { delete() }
.keyboardShortcut(.delete, modifiers: .command)
Posicionamentos de CommandGroup: .newItem, .saveItem, .help, .toolbar, .sidebar
Use .replacing(_:) para substituir um grupo do sistema.
Modo: views
HSplitView / VSplitView (macOS-only)
Use para layouts IDE-style onde todos os painéis são pares iguais. Para navegação
sidebar → conteúdo, prefira NavigationSplitView.
HSplitView {
FileTreeView()
.frame(minWidth: 200)
CodeEditorView()
.frame(minWidth: 400)
PreviewPane()
.frame(minWidth: 200)
}
VSplitView é idêntico mas divide verticalmente (use minHeight em vez de minWidth).
Table (macOS 12.0+)
struct PeopleTable: View {
@State private var people: [Person] = Person.samples
@State private var selection: Set<Person.ID> = []
@State private var sortOrder = [KeyPathComparator(\Person.name)]
var body:
---
*Content truncated.*