Application de barre de menus pour macOS

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 QuotesViewController

Ajoutez maintenant le code suivant Ă  la fin du fichier QuotesViewController.swift :

 extension QuotesViewController { // MARK: Storyboard instantiation static func freshController() -> QuotesViewController { //1. let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) //2. let identifier = NSStoryboard.SceneIdentifier("QuotesViewController") //3. guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? QuotesViewController else { fatalError("Why cant i find QuotesViewController? - Check Main.storyboard") } return viewcontroller } } 

Que se passe-t-il ici:

  1. nous obtenons un lien vers Main.storyboard .
  2. créer un identifiant de scène qui correspond à celui que nous venons d'installer juste au-dessus.
  3. 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.

 // MARK: Actions extension QuotesViewController { @IBAction func previous(_ sender: NSButton) { } @IBAction func next(_ sender: NSButton) { } @IBAction func quit(_ sender: NSButton) { } } 

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 standard

Ouvrez 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 .

Source: https://habr.com/ru/post/fr447754/


All Articles