Comment créer une menu bar utile sur macOS avec SwiftUI

Barre de menu - SwiftUI

Introduction

Après avoir développé un outil macOS pour nettoyer les caches développeur, une question s’est rapidement posée :
Comment rendre cet outil accessible en permanence, sans alourdir l’expérience utilisateur ?

Sur macOS, la réponse est souvent évidente : la menu bar.

Discrète, toujours accessible et parfaitement intégrée au système, elle permet de proposer des actions rapides sans passer par une fenêtre classique. Pourtant, sa mise en place avec SwiftUI soulève quelques subtilités, notamment si l’on veut aller au-delà d’un simple menu statique.

Dans cet article, nous allons voir comment créer une menu bar utile, performante et bien intégrée avec SwiftUI, en nous concentrant sur des cas concrets et des bonnes pratiques.


Pourquoi utiliser une menu bar sur macOS ?

Avant de plonger dans l’implémentation, il est utile de comprendre pourquoi ce type d’interface est particulièrement adapté à certains outils.

Une application en menu bar répond généralement à trois objectifs. D’abord, proposer un accès rapide à des fonctionnalités fréquentes. C’est typiquement le cas pour un outil de nettoyage de caches, de monitoring ou pour tout utilitaire que l’on veut garder à portée de clic.

Ensuite, elle évite d’encombrer le Dock ou le bureau avec une application complète qui n’a pas besoin d’être ouverte en permanence. Enfin, elle offre une expérience fluide et native, parfaitement alignée avec les usages de macOS.

Dans mon cas, afficher l’espace disque utilisé par les caches et proposer un bouton de nettoyage directement depuis la barre de menu était beaucoup plus pertinent qu’une application classique.


Les bases : créer une MenuBarExtra avec SwiftUI

Depuis macOS 13, SwiftUI propose une API dédiée : MenuBarExtra.

Elle simplifie fortement la création d’applications de menu bar, sans devoir s’appuyer directement sur AppKit.

Déclaration de l’application

On peut commencer avec une application SwiftUI très simple, sans fenêtre principale :

@main
struct CacheCleanerApp: App {
    var body: some Scene {
        MenuBarExtra("Cache Cleaner", systemImage: "externaldrive") {
            ContentView()
        }
    }
}

Avec cette approche, l’application n’affiche aucune fenêtre principale. Elle vit uniquement dans la menu bar.

MenuBar

Construire une interface utile, et pas juste un menu

Une erreur fréquente consiste à créer un menu basique avec quelques actions statiques. Cela fonctionne pour des besoins très simples, mais devient vite limité dès que l’on souhaite afficher une interface un peu plus riche.

Mais avec SwiftUI, on peut aller beaucoup plus loin…

Par défaut, MenuBarExtra se comporte comme un menu classique macOS. Cette structure convient pour une poignée d’actions, mais devient contraignante si l’on veut construire quelque chose de plus lisible et plus flexible.

C’est là qu’intervient une option importante : .menuBarExtraStyle(.window).

Activer le mode window

En ajoutant ce modificateur, on transforme le comportement de la menu bar. Au lieu d’un menu classique, on obtient une véritable fenêtre SwiftUI, beaucoup plus souple à organiser.

@main
struct CacheCleanerApp: App {
    var body: some Scene {
        MenuBarExtra("Cache Cleaner", systemImage: "externaldrive") {
            ContentView()
        }
        .menuBarExtraStyle(.window)
    }
}

Avec cette configuration, il devient possible d’utiliser plus librement les composants SwiftUI, comme VStackHStack, les séparateurs, les états dynamiques ou encore une mise en page plus soignée.

Exemple d’interface

struct ContentView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Caches utilisés : 12 Go")
                .font(.headline)
            Divider()
            HStack {
                Text("Android/Gradle Caches")
                Spacer()
                Button("Nettoyer les caches") {
                    // action de nettoyage
                }
            }
            Divider()
            HStack {
                Spacer()
                Menu {
                    Button("À propos") {
                        // ouvrir une fenêtre
                    }
                } label: {
                    Image(systemName: "gearshape")
                }
            }
        }
        .padding()
        .frame(width: 400)
    }
}

Ce type d’interface transforme une simple icône de menu bar en véritable point d’entrée pour l’outil. On n’est plus dans un simple menu technique, mais dans une petite interface claire, rapide et agréable à utiliser.

MenuBar

Masquer l’application du Dock et du sélecteur d’applications

Lorsque l’on développe une application de menu bar, l’objectif est souvent de la rendre discrète et toujours disponible, sans polluer l’interface principale de macOS.

Par défaut, même avec MenuBarExtra, l’application apparaît encore dans le Dock et dans le sélecteur d’applications avec Cmd + Tab. Pour un utilitaire système ou un outil de nettoyage de caches, ce comportement n’est pas toujours souhaitable.

Configurer le type d’application

Pour corriger cela, il faut indiquer à macOS que l’application est un agent, autrement dit une application qui fonctionne sans interface principale classique.

Cela se fait dans le fichier Info.plist avec la clé suivante :

<key>LSUIElement</key>
<true/>

Une fois cette option activée, l’application disparaît du Dock et du sélecteur d’applications, tout en restant accessible depuis la menu bar.

MenuBar

Attention aux effets de bord

Ce mode implique aussi quelques conséquences. L’application ne peut plus être activée de manière classique, et si vous avez besoin d’ouvrir une fenêtre de préférences, d’aide ou de détails techniques, vous devrez gérer explicitement son affichage.

Certaines interactions système peuvent également demander des ajustements, notamment si votre application repose sur le focus, des raccourcis clavier globaux ou des comportements proches d’une application standard.


Permettre à l’utilisateur de quitter l’application proprement

Une fois l’application masquée du Dock, une question simple devient importante : comment la quitter ?

Dans une application classique, macOS fournit déjà plusieurs moyens de fermeture. Mais dans une application de menu bar, surtout avec LSUIElement, ce point de sortie doit être prévu explicitement.

Ajouter une action Quitter

La solution la plus simple consiste à ajouter un bouton directement dans l’interface :

Button("Quitter") {
    NSApplication.shared.terminate(nil)
}

Cette approche est simple, efficace et cohérente avec les usages macOS. Elle évite à l’utilisateur de se retrouver avec une application active sans moyen évident de la fermer.

Ce détail peut sembler mineur, mais il joue beaucoup sur la perception de qualité. Une application impossible à quitter donne tout de suite une impression de manque de finition.


Aller plus loin : ouvrir une fenêtre depuis la menu bar

Même si une application de menu bar doit rester légère, il peut être utile d’ouvrir une fenêtre secondaire pour afficher certaines informations complémentaires. C’est particulièrement pertinent pour une vue About, une page de préférences, un écran d’aide ou des détails techniques.

L’idée n’est pas de recréer une application desktop complète, mais d’ajouter quelques fenêtres ciblées lorsque cela apporte une vraie valeur.

Déclarer une fenêtre dédiée avec SwiftUI

SwiftUI permet de définir directement une fenêtre nommée dans la structure de l’application. Cela offre une séparation claire entre la menu bar et les vues secondaires.

Par exemple, pour afficher une fenêtre About, on peut déclarer une scène Window dédiée :

@main
struct CacheCleanerApp: App {
    var body: some Scene {
        MenuBarExtra("Cache Cleaner", systemImage: "externaldrive") {
            ContentView()
        }
        .menuBarExtraStyle(.window)

        Window("About DevCacheCleaner", id: "about-dev-cache-cleaner") {
            AboutView()
                .windowMinimizeBehavior(.disabled)
                .containerBackground(.regularMaterial, for: .window)
        }
        .windowStyle(.hiddenTitleBar)
        .windowResizability(.contentSize)
    }
}

Cette configuration permet de créer une vraie fenêtre macOS avec une présentation plus soignée et mieux contrôlée qu’une fenêtre standard.

Pourquoi cette configuration est intéressante

Le fait d’utiliser une scène Window avec un identifiant explicite permet d’ouvrir la fenêtre à la demande depuis n’importe quelle vue SwiftUI. C’est une solution propre, lisible et bien plus élégante que de bricoler une ouverture manuelle avec AppKit.

L’utilisation de .windowStyle(.hiddenTitleBar) permet d’obtenir une fenêtre plus épurée, ce qui fonctionne particulièrement bien pour une vue About ou une petite fenêtre informative.

Avec .windowResizability(.contentSize), la taille s’adapte au contenu, ce qui évite les fenêtres surdimensionnées. Enfin, .containerBackground(.regularMaterial, for: .window) apporte un rendu visuel plus naturel et mieux intégré à l’esthétique récente de macOS.

Ouvrir la fenêtre avec openWindow

Une fois la fenêtre déclarée, il suffit d’utiliser openWindow pour l’ouvrir depuis la menu bar :

struct ContentView: View {

    @Environment(\.openWindow) var openWindow
    
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Caches utilisés : 12 Go")
                .font(.headline)
            Divider()
            HStack {
                Text("Android/Gradle Caches")
                Spacer()
                Button("Nettoyer les caches") {
                    // action de nettoyage
                }
            }
            Divider()
            HStack {
                Spacer()
                Menu {
                    Button("À propos") {
                        openWindow(id: "about-dev-cache-cleaner")
                    }
                    Divider()
                    Button("Quit", systemImage: "close") {
                        NSApp.terminate(nil)
                    }.keyboardShortcut("q", modifiers: [.control])
                } label: {
                    Image(systemName: "gearshape")
                }
            }
        }
        .padding()
        .frame(width: 400)
    }
}

Cette approche reste totalement dans la logique SwiftUI. On déclare une fenêtre avec un identifiant, puis on l’ouvre proprement là où on en a besoin.

Garder l’esprit d’une application de menu bar

Travailler sur une application de menu bar avec SwiftUI montre rapidement que la simplicité apparente cache des choix importants. Passer d’un menu classique à une vraie interface avec .menuBarExtraStyle(.window) change complètement la manière de concevoir l’outil.

Ce type d’application impose d’aller à l’essentiel. L’interface doit être rapide, claire et toujours à jour, ce qui pousse à structurer proprement la gestion de l’état et à éviter toute logique bloquante.

La gestion des fenêtres secondaires avec Window et openWindow apporte aussi une vraie clarté d’architecture, en gardant une séparation nette entre les vues principales et les fonctionnalités complémentaires.

Enfin, masquer l’application du Dock transforme la perception du produit. On ne construit plus une application classique, mais un outil discret, pensé pour être utilisé au quotidien sans friction.

Conclusion

Mettre en place une application de menu bar sur macOS avec SwiftUI ne se résume pas à afficher quelques actions dans un menu. Avec les bonnes approches, notamment l’utilisation de .menuBarExtraStyle(.window) et la gestion de fenêtres secondaires via Window, il est possible de construire de véritables outils à la fois légers, rapides et agréables à utiliser au quotidien.

Ce type d’application est particulièrement adapté aux utilitaires développeur, comme un outil de nettoyage de caches, où l’accès rapide à l’information et aux actions est essentiel. En combinant une interface minimaliste dans la menu bar et des fenêtres dédiées pour les fonctionnalités secondaires, on obtient un bon équilibre entre simplicité et puissance.

Mais au-delà de la technique, c’est surtout une manière différente de concevoir un produit. Une bonne application de barre de menu ne cherche pas à tout faire, mais à faire peu de choses, de manière efficace et immédiate.

C’est exactement ce type de réflexion que j’applique dans mes projets : construire des outils utiles, bien intégrés, et pensés pour être réellement utilisés.

Si vous travaillez sur un utilitaire macOS ou un outil interne, prendre le temps de concevoir une bonne expérience en menu bar peut faire une vraie différence sur l’adoption et l’usage au quotidien.


Article similaire


Besoin d’un regard technique sur votre projet ?
Je peux vous accompagner sur vos développements.