Les applications situées dans la barre de menus sont connues depuis longtemps des utilisateurs de macOS. Certaines de ces applications ont une partie «normale», d'autres se trouvent uniquement dans la barre de menus.
Dans ce guide, vous allez écrire une application qui affiche plusieurs citations de personnes célèbres dans une fenêtre pop-up. Au cours de la création de cette application, vous apprendrez:
- attribuer une icĂ´ne d'application dans la barre de menus
- faire l'application placée uniquement dans la barre de menu
- ajouter un menu personnalisé
- afficher une fenêtre contextuelle à la demande de l'utilisateur et la masquer si nécessaire, à l'aide de la surveillance des événements
Remarque: ce guide suppose que vous connaissez bien Swift et macOS.
Pour commencer
Lancez Xcode. Ensuite, dans le menu 
Fichier / Nouveau / Projet ... , sélectionnez le modèle d' 
application macOS / Application / Cocoa et cliquez sur 
Suivant .
Sur l'écran suivant, entrez Devis comme 
nom de produit , sélectionnez votre 
nom d' organisation et votre 
identifiant d'organisation . Assurez-vous ensuite que Swift est sélectionné comme langue d'application et que la case 
Utiliser les storyboards est cochée. Décochez les 
cases Créer une application basée sur un document , 
Utiliser les données de base , 
Inclure les tests unitaires et 
Inclure les tests d'interface utilisateur .

Enfin, cliquez Ă  nouveau sur 
Suivant , spécifiez l'emplacement où enregistrer le projet et cliquez sur 
Créer .
Une fois le nouveau projet créé, ouvrez 
AppDelegate.swift et ajoutez la propriété suivante à la classe:
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength) 
Ici, nous créons dans la barre de menu Élément d'état (icône d'application) une longueur fixe qui sera visible pour les utilisateurs.
Ensuite, nous devons affecter notre image à ce nouvel élément dans la barre de menu afin de pouvoir distinguer notre nouvelle application.
Dans le navigateur de projet, accédez à Assets.xcassets, 
téléchargez une image et faites-la glisser dans le catalogue d'actifs.
Sélectionnez une image et ouvrez l'inspecteur d'attributs. Modifiez l'option 
Rendre en tant qu'image de 
modèle .

Si vous utilisez votre propre image, assurez-vous que l'image est en noir et blanc et configurez-la en tant 
qu'image de modèle afin que l'icône soit superbe à la fois dans la barre de menu sombre et claire.
Revenez Ă  
AppDelegate.swift et ajoutez le code suivant Ă  
applicationDidFinishLaunching (_ :) if let button = statusItem.button { button.image = NSImage(named:NSImage.Name("StatusBarButtonImage")) button.action = #selector(printQuote(_:)) } 
Ici, nous attribuons l'icĂ´ne d'application que nous venons d'ajouter Ă  l'icĂ´ne d'application et attribuons une 
action lorsque nous cliquons dessus.
Ajoutez la méthode suivante à la classe:
 @objc func printQuote(_ sender: Any?) { let quoteText = "Never put off until tomorrow what you can do the day after tomorrow." let quoteAuthor = "Mark Twain" print("\(quoteText) — \(quoteAuthor)") } 
Cette méthode imprime simplement le devis sur la console.
Faites attention Ă  la 
directive de méthode 
objc . Cela vous permet d'utiliser cette méthode en réponse à un clic sur un bouton.
Générez et exécutez l'application, et vous verrez la nouvelle application dans la barre de menus. Hourra!
Chaque fois que vous cliquez sur l'icône dans la barre de menu, le célèbre dicton de Mark Twain s'affiche dans la console Xcode.
Nous masquons la fenĂŞtre principale et l'icĂ´ne dans le dock
Il y a quelques petites choses que nous devons faire avant de traiter directement avec la fonctionnalité:
- supprimer l'icĂ´ne du quai
- supprimer la fenĂŞtre principale de l'application inutile
Pour supprimer l'icĂ´ne du dock, ouvrez 
Info.plist . Ajoutez une nouvelle clé 
Application is agent (UIElement) et définissez sa valeur sur 
YES .

Il est maintenant temps de traiter avec la fenĂŞtre principale de l'application.
- ouvrir Main.storyboard
- sélectionnez la scène du contrôleur de fenêtre et supprimez-la
- Voir la scène du contrôleur quitter, nous l'utiliserons bientôt

Générez et exécutez l'application. Maintenant, l'application n'a pas à la fois la fenêtre principale et l'icône inutile dans le dock. Super!
Ajouter un menu à l'élément d'état
Une réponse en un seul clic n'est clairement pas suffisante pour une application sérieuse. La façon la plus simple d'ajouter des fonctionnalités consiste à ajouter un menu. Ajoutez cette fonction à la fin d' 
AppDelegate .
 func constructMenu() { let menu = NSMenu() menu.addItem(NSMenuItem(title: "Print Quote", action: #selector(AppDelegate.printQuote(_:)), keyEquivalent: "P")) menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem(title: "Quit Quotes", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) statusItem.menu = menu } 
Et puis ajoutez cet appel Ă  la fin de 
applicationDidFinishLaunching (_ :) constructMenu() 
Nous créons 
NSMenu , y ajoutons 3 instances de 
NSMenuItem et définissons ce menu comme menu d'icônes d'application.
Quelques points importants:
- Le titre de l'élément de menu est le texte qui apparaît dans le menu. Un bon endroit pour localiser l'application (si nécessaire).
- l'action , comme l' action d'un bouton ou d'un autre contrôle, est une méthode qui est appelée lorsque l'utilisateur clique sur un élément de menu
- keyEquivalent est un raccourci clavier que vous pouvez utiliser pour sélectionner un élément de menu. Les caractères minuscules utilisent Cmd comme modificateur et les caractères minuscules utilisent Cmd + Maj . Cela ne fonctionne que si l'application est tout en haut et est active. Dans notre cas, il est nécessaire que le menu ou une autre fenêtre soit visible, car notre application n'a pas d'icône dans le dock
- separatorItem est un élément de menu inactif sous la forme d'une ligne grise entre d'autres éléments. Utilisez-le pour regrouper
- printQuote est la méthode que vous avez déjà définie dans AppDelegate et terminate est la méthode définie par NSApplication .
Lancez l'application et vous verrez un menu en cliquant sur l'icĂ´ne de l'application.

Essayez de cliquer sur le menu - sélectionner 
Imprimer le devis affiche le devis dans la console Xcode et 
Quitter les devis met fin Ă  l'application.
Ajouter une popup
Vous avez vu à quel point il est facile d'ajouter un menu à partir du code, mais afficher un devis dans la console Xcode n'est clairement pas ce que les utilisateurs attendent de l'application. Nous allons maintenant ajouter un simple contrôleur de vue pour afficher les devis de la bonne manière.
Allez dans le menu 
Fichier / Nouveau / Fichier ... , sélectionnez le modèle de 
classe macOS / Source / Cocoa et cliquez sur 
Suivant .

- nommer la classe QuotesViewController
- faire un héritier de NSViewController
- assurez-vous que la case à cocher Créer également un fichier XIB pour l'interface utilisateur n'est pas cochée
- définir la langue sur Swift
Enfin, cliquez Ă  nouveau sur 
Suivant , sélectionnez un emplacement pour enregistrer le fichier, puis cliquez sur 
Créer .
Maintenant, ouvrez 
Main.storyboard . Développez 
View Controller Scene et sélectionnez 
View Controller instance .

Sélectionnez d'abord l' 
inspecteur d'identité et changez la classe en 
QuotesViewController , puis définissez l' 
ID du 
storyboard sur 
QuotesViewControllerAjoutez maintenant le code suivant Ă  la fin du fichier 
QuotesViewController.swift :
 extension QuotesViewController {  
Que se passe-t-il ici:
- nous obtenons un lien vers Main.storyboard .
- créer un identifiant de scène qui correspond à celui que nous venons d'installer juste au-dessus.
- créez une instance de QuotesViewController et renvoyez-la.
Vous créez cette méthode, donc maintenant, tous ceux qui utilisent le 
QuotesViewController n'ont pas besoin de savoir comment il est créé. Ça marche juste.
Notez le 
fatalError à l'intérieur de l'instruction 
guard . Il peut être agréable de l'utiliser ou d' 
assertionFailure de sorte que si quelque chose se passe mal dans le développement, vous-même et les autres membres de l'équipe de développement soyez au courant.
Revenons maintenant Ă  
AppDelegate.swift . Ajoutez une nouvelle propriété.
 let popover = NSPopover() 
Remplacez ensuite un 
pplicationDidFinishLaunching (_ :) par le code suivant:
 func applicationDidFinishLaunching(_ aNotification: Notification) { if let button = statusItem.button { button.image = NSImage(named:NSImage.Name("StatusBarButtonImage")) button.action = #selector(togglePopover(_:)) } popover.contentViewController = QuotesViewController.freshController() } 
Vous avez modifié l'action de clic pour appeler la 
méthode togglePopover (_ :) , que nous écrirons un peu plus tard. De plus, au lieu de configurer et d'ajouter un menu, nous avons configuré une fenêtre contextuelle qui affichera quelque chose de 
QuotesViewController .
Ajoutez les trois méthodes suivantes à 
AppDelegate :
 @objc func togglePopover(_ sender: Any?) { if popover.isShown { closePopover(sender: sender) } else { showPopover(sender: sender) } } func showPopover(sender: Any?) { if let button = statusItem.button { popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) } } func closePopover(sender: Any?) { popover.performClose(sender) } 
showPopover () affiche une fenêtre contextuelle. Vous indiquez simplement d'où il vient, macOS le positionne et dessine une flèche, comme s'il apparaissait dans la barre de menus.
closePopover () ferme simplement la fenĂŞtre contextuelle et 
togglePopover () est une méthode qui affiche ou masque la fenêtre contextuelle, selon son état.
Lancez l'application et cliquez sur son icĂ´ne.

Tout va bien, mais oĂą est le contenu?
Nous implémentons Quote View Controller
Vous avez d'abord besoin d'un modèle pour stocker les citations et les attributs. Allez dans le menu 
Fichier / Nouveau / Fichier ... et sélectionnez le 
modèle de fichier macOS / Source / Swift , puis 
Suivant . Nommez le fichier 
Quote et cliquez sur 
Créer .
Ouvrez le fichier 
Quote.swift et ajoutez-y le code suivant:
 struct Quote { let text: String let author: String static let all: [Quote] = [ Quote(text: "Never put off until tomorrow what you can do the day after tomorrow.", author: "Mark Twain"), Quote(text: "Efficiency is doing better what is already being done.", author: "Peter Drucker"), Quote(text: "To infinity and beyond!", author: "Buzz Lightyear"), Quote(text: "May the Force be with you.", author: "Han Solo"), Quote(text: "Simplicity is the ultimate sophistication", author: "Leonardo da Vinci"), Quote(text: "It's not just what it looks like and feels like. Design is how it works.", author: "Steve Jobs") ] } extension Quote: CustomStringConvertible { var description: String { return "\"\(text)\" — \(author)" } } 
Ici, nous définissons une structure de devis simple et une propriété statique qui renvoie tous les devis. Puisque nous avons rendu 
Quote compatible avec le protocole 
CustomStringConvertible , nous pouvons facilement obtenir du texte formaté de manière pratique.
Il y a des progrès, mais nous avons encore besoin de contrôles pour afficher tout cela.
Ajouter des éléments d'interface
Ouvrez le tableau principal et retirez 3 boutons ( 
bouton- poussoir) et l'étiquette (étiquette 
multiligne) sur le contrĂ´leur de vue.
Positionnez les boutons et l'étiquette de manière à ce qu'ils ressemblent à ceci:

Fixez le bouton gauche au bord gauche avec un écart de 20 et centrez verticalement.
Fixez le bouton droit au bord droit avec un écart de 20 et centrez verticalement.
Fixez le bouton inférieur au bord inférieur avec un espace de 20 et centrez horizontalement.
Fixez les bords gauche et droit de la marque aux boutons avec un espace de 20, centrez verticalement.

Vous verrez plusieurs erreurs de mise en page, car il n'y a pas suffisamment d'informations pour que 
la mise en 
page automatique puisse le comprendre.
Définissez la 
Priorité d'étirement du contenu horizontal sur 249 pour permettre à l'étiquette de se redimensionner.

Procédez maintenant comme suit:
- définissez l' image du bouton gauche sur NSGoLeftTemplate et effacez le titre
- définissez l' image du bouton droit sur NSGoRightTemplate et effacez le titre
- définissez le titre du bouton ci-dessous sur Quitter les devis .
- définissez l' alignement du texte de l'étiquette au centre.
- vérifiez que le saut de ligne sur l'étiquette est défini sur Retour à la ligne .
Ouvrez maintenant 
QuotesViewController.swift et ajoutez le code suivant à l'implémentation de la classe 
QuotesViewController :
 @IBOutlet var textLabel: NSTextField! 
Ajoutez cette extension à l'implémentation de classe. Maintenant, dans 
QuotesViewController.swift, il y a deux extensions de classe.
 
Nous venons d'ajouter une 
sortie pour l'étiquette que nous utiliserons pour afficher les citations et 3 méthodes de stub que nous connecterons avec les boutons.
Connexion du code avec Interface Builder
Remarque: Xcode a placé des cercles à gauche de votre code - à côté des mots clés 
IBAction et 
IBOutlet .

Nous les utiliserons pour connecter le code Ă  l'interface utilisateur.
Tout en maintenant la touche 
alt enfoncée, cliquez sur 
Main.storyboard dans le 
navigateur de projet . Ainsi, le 
storyboard s'ouvre dans l' 
Assistant Editor Ă  droite et le code Ă  gauche.
Faites glisser le cercle Ă  gauche de 
textLabel sur l'étiquette dans le 
générateur d'interface . De la même manière, combinez les méthodes 
précédente , 
suivante et 
quitter avec les boutons gauche, droit et bas, respectivement.

Lancez votre application.

Nous avons utilisé la taille du popup par défaut. Si vous voulez un popup plus grand ou plus petit, redimensionnez-le simplement dans le 
storyboard .
Écrire un code pour les boutons
Si vous n'avez pas déjà masqué l' 
Editeur Assistant , cliquez sur 
Cmd-Retour ou 
Vue> Editeur standard> Afficher l'éditeur standardOuvrez 
QuotesViewController.swift et ajoutez les propriétés suivantes à l'implémentation de classe:
 let quotes = Quote.all var currentQuoteIndex: Int = 0 { didSet { updateQuote() } } 
La propriété 
quotes contient toutes les citations et 
currentQuoteIndex est l'index de la citation actuellement affichée. 
CurrentQuoteIndex possède également un 
observateur de propriétés pour mettre à jour le contenu de l'étiquette avec un nouveau devis lorsque l'index change.
Ajoutez maintenant les méthodes suivantes:
 override func viewDidLoad() { super.viewDidLoad() currentQuoteIndex = 0 } func updateQuote() { textLabel.stringValue = String(describing: quotes[currentQuoteIndex]) } 
Lorsque la vue se charge, nous définissons l'index des devis à 0, ce qui entraîne une mise à jour de l'interface. 
updateQuote () met simplement à jour l'étiquette de texte pour afficher un devis. 
currentQuoteIndex correspondant.
Enfin, mettez à jour ces méthodes avec le code suivant:
 @IBAction func previous(_ sender: NSButton) { currentQuoteIndex = (currentQuoteIndex - 1 + quotes.count) % quotes.count } @IBAction func next(_ sender: NSButton) { currentQuoteIndex = (currentQuoteIndex + 1) % quotes.count } @IBAction func quit(_ sender: NSButton) { NSApplication.shared.terminate(sender) } 
Les méthodes 
next () et 
previous () parcourent toutes les citations. 
quitter ferme l'application.
Lancez l'application:

Suivi des événements
Il y a encore une chose que les utilisateurs attendent de notre application: masquer la fenêtre contextuelle lorsque l'utilisateur clique quelque part en dehors de celle-ci. Pour ce faire, nous avons besoin d'un mécanisme appelé 
moniteur d'événements global macOS .
Créez un nouveau fichier Swift, appelez-le 
EventMonitor et remplacez son contenu par le code suivant:
 import Cocoa public class EventMonitor { private var monitor: Any? private let mask: NSEvent.EventTypeMask private let handler: (NSEvent?) -> Void public init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) { self.mask = mask self.handler = handler } deinit { stop() } public func start() { monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler) } public func stop() { if monitor != nil { NSEvent.removeMonitor(monitor!) monitor = nil } } } 
Lors de l'initialisation d'une instance de cette classe, nous lui transmettons un masque d'événement que nous écouterons (comme les frappes de touches, les molettes de la souris, etc.) et un gestionnaire d'événements.
Lorsque nous sommes prêts à commencer à écouter, 
start () appelle 
addGlobalMonitorForEventsMatchingMask (_: handler :) , qui renvoie l'objet que nous enregistrons. Dès que l'événement contenu dans le masque se produit, le système appelle votre gestionnaire.
Pour arrêter la surveillance des événements, 
removeMonitor () est appelé dans 
stop () et nous supprimons l'objet en le 
définissant sur nil.
Il ne nous reste plus qu'Ă  appeler 
start () et 
stop () au bon moment. La classe appelle également 
stop () sur le deinitializer pour nettoyer.
Connexion du moniteur d'événements
Ouvrez 
AppDelegate.swift une dernière fois et ajoutez une nouvelle propriété:
 var eventMonitor: EventMonitor? 
Ajoutez ensuite ce code pour configurer le 
moniteur d'événements à la fin de 
applicationDidFinishLaunching (_ :) eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in if let strongSelf = self, strongSelf.popover.isShown { strongSelf.closePopover(sender: event) } } 
Cela informera votre application lorsque vous cliquez sur le bouton gauche ou droit. Veuillez noter: le gestionnaire ne sera pas appelé en réponse aux clics de souris dans votre application. C'est pourquoi la fenêtre contextuelle ne se fermera pas lorsque vous cliquez à l'intérieur.
Nous utilisons une référence 
faible Ă  
soi pour éviter le danger d'un cycle de liens solides entre 
AppDelegate et 
EventMonitor .
Ajoutez le code suivant Ă  la fin de la 
méthode showPopover (_ :) :
 eventMonitor?.start() 
Ici, nous commençons à surveiller les événements lorsqu'une fenêtre contextuelle apparaît.
Ajoutez maintenant le code Ă  la fin de la 
méthode closePopover (_ :) :
 eventMonitor?.stop() 
Ici, nous terminons la surveillance lorsque le popup se ferme.
L'application est prĂŞte!
Conclusion
Vous trouverez ici le code complet de ce projet.
Vous avez appris à configurer le menu et la fenêtre contextuelle dans l'application située dans la barre de menus. Pourquoi ne pas essayer plusieurs balises ou du texte formaté pour mieux voir les citations? Ou connectez-vous à un backend pour recevoir des devis d'Internet? Ou voulez-vous utiliser le clavier pour naviguer entre les guillemets?
Un bon endroit pour rechercher est la documentation officielle: 
NSMenu , 
NSPopover et 
NSStatusItem .