Aplicación de barra de menú para macOS

Las aplicaciones ubicadas en la barra de menú han sido conocidas por los usuarios de macOS. Algunas de estas aplicaciones tienen una parte "normal", otras se encuentran solo en la barra de menú.
En esta guía, escribirá una aplicación que muestra varias citas de personas famosas en una ventana emergente. En el proceso de creación de esta aplicación, aprenderá:

  • asignar icono de la aplicación en la barra de menú
  • hacer que la aplicación esté alojada solo en la barra de menú
  • agregar menú personalizado
  • muestra una ventana emergente a solicitud del usuario y la oculta cuando es necesario, utilizando Event Monitoring

Nota: esta guía asume que está familiarizado con Swift y macOS.

Empezando


Lanzamiento de Xcode. A continuación, en el menú Archivo / Nuevo / Proyecto ... , seleccione la plantilla macOS / Aplicación / Aplicación de Cocoa y haga clic en Siguiente .

En la siguiente pantalla, ingrese Cotizaciones como el Nombre del producto , seleccione su Nombre de organización e Identificador de organización . Luego, asegúrese de que Swift esté seleccionado como el idioma de la aplicación y que la casilla de verificación Usar guiones gráficos esté marcada. Desmarque las casillas de verificación Crear aplicación basada en documentos , Usar datos básicos , Incluir pruebas unitarias e Incluir pruebas de IU .



Finalmente, haga clic en Siguiente nuevamente, especifique la ubicación para guardar el proyecto y haga clic en Crear .
Una vez que se crea el nuevo proyecto, abra AppDelegate.swift y agregue la siguiente propiedad a la clase:

let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength) 

Aquí creamos en la barra de menú Elemento de estado (icono de la aplicación) una longitud fija que será visible para los usuarios.

Luego, debemos asignar nuestra imagen a este nuevo elemento en la barra de menú para que podamos distinguir nuestra nueva aplicación.

En el navegador de proyectos, vaya a Assets.xcassets, cargue una imagen y arrástrela al catálogo de activos.

Seleccione una imagen y abra el inspector de atributos. Cambie la opción Renderizar como a Imagen de plantilla .



Si usa su propia imagen, asegúrese de que la imagen sea en blanco y negro y configúrela como una imagen de plantilla para que el icono se vea bien tanto en la barra de menú oscura como en la clara.

Regrese a AppDelegate.swift y agregue el siguiente código a applicationDidFinishLaunching (_ :)

 if let button = statusItem.button { button.image = NSImage(named:NSImage.Name("StatusBarButtonImage")) button.action = #selector(printQuote(_:)) } 

Aquí asignamos el icono de la aplicación que acabamos de agregar al icono de la aplicación y asignamos acción cuando hacemos clic en él.

Agregue el siguiente método a la clase:

 @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)") } 

Este método simplemente imprime la cita en la consola.

Presta atención a la directiva del método objc . Esto le permite utilizar este método como respuesta a un clic de botón.

Compile y ejecute la aplicación, y verá la nueva aplicación en la barra de menú. ¡Hurra!
Cada vez que hace clic en el icono en la barra de menú, el famoso dicho de Mark Twain se muestra en la consola Xcode.

Ocultamos la ventana principal y el ícono en el dock


Hay un par de pequeñas cosas que debemos hacer antes de tratar directamente con la funcionalidad:

  • eliminar icono del muelle
  • eliminar la ventana principal de la aplicación innecesaria

Para eliminar el icono del dock, abra Info.plist . Agregue una nueva clave Aplicación es agente (UIElement) y establezca su valor en .



Ahora es el momento de lidiar con la ventana principal de la aplicación.

  • abrir Main.storyboard
  • seleccione la escena del controlador de ventana y elimínela
  • Ver la escena del controlador , la usaremos pronto




Compila y ejecuta la aplicación. Ahora la aplicación no tiene tanto la ventana principal como el icono innecesario en el dock. Genial

Agregar menú al elemento de estado


Una respuesta de un solo clic claramente no es suficiente para una aplicación seria. La forma más fácil de agregar funcionalidad es agregar un menú. Agregue esta función al final de 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 } 

Y luego agregue esta llamada al final de applicationDidFinishLaunching (_ :)

 constructMenu() 

Creamos NSMenu , le agregamos 3 instancias de NSMenuItem y configuramos este menú como el menú del icono de la aplicación.

Algunos puntos importantes:

  • El título del elemento del menú es el texto que aparece en el menú. Un buen lugar para localizar la aplicación (si es necesario).
  • La acción , como la acción de un botón u otro control, es un método que se llama cuando el usuario hace clic en un elemento del menú
  • keyEquivalent es un método abreviado de teclado que puede usar para seleccionar un elemento del menú. Los caracteres en minúscula usan Cmd como modificador, y los caracteres en minúscula usan Cmd + Shift . Esto solo funciona si la aplicación está en la parte superior y está activa. En nuestro caso, es necesario que el menú o alguna otra ventana esté visible, ya que nuestra aplicación no tiene un ícono en el dock
  • separatorItem es un elemento de menú inactivo en forma de una línea gris entre otros elementos. Úselo para agrupar
  • printQuote es el método que ya definió en AppDelegate , y terminar es el método definido por NSApplication .

Inicie la aplicación y verá un menú haciendo clic en el icono de la aplicación.



Intente hacer clic en el menú: al seleccionar Imprimir cotización, se muestra la cotización en la consola de Xcode y se cierra la aplicación.

Agregar una ventana emergente


Viste lo fácil que es agregar un menú desde el código, pero mostrar una cita en la consola Xcode claramente no es lo que los usuarios esperan de la aplicación. Ahora agregaremos un controlador de vista simple para mostrar las cotizaciones de la manera correcta.

Vaya al menú Archivo / Nuevo / Archivo ... , seleccione la plantilla macOS / Source / Cocoa Class y haga clic en Siguiente .



  • nombrar la clase QuotesViewController
  • hacer un heredero de NSViewController
  • asegúrese de que la casilla de verificación Crear también archivo XIB para la interfaz de usuario no esté marcada
  • establecer el idioma en Swift

Finalmente, haga clic en Siguiente nuevamente, seleccione una ubicación para guardar el archivo y haga clic en Crear .
Ahora abra Main.storyboard . Expanda Ver escena del controlador y seleccione Ver instancia del controlador .



Primero seleccione el Inspector de identidad y cambie la clase a QuotesViewController , luego establezca el ID del guión gráfico en QuotesViewController

Ahora agregue el siguiente código al final del archivo 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 } } 

¿Qué está pasando aquí?

  1. obtenemos un enlace a Main.storyboard .
  2. cree un identificador de escena que coincida con el que acabamos de instalar justo arriba.
  3. cree una instancia de QuotesViewController y devuélvala.

Usted crea este método, por lo que ahora todos los que usan QuotesViewController no necesitan saber cómo se crea. Simplemente funciona

Tenga en cuenta el error fatal dentro de la declaración de guardia . Puede ser bueno usarlo o afirmar Falla para que si algo en el desarrollo sale mal, usted y los otros miembros del equipo de desarrollo estén informados.

Ahora de vuelta a AppDelegate.swift . Agregar una nueva propiedad.

 let popover = NSPopover() 

Luego reemplace un pplicationDidFinishLaunching (_ :) con el siguiente código:

 func applicationDidFinishLaunching(_ aNotification: Notification) { if let button = statusItem.button { button.image = NSImage(named:NSImage.Name("StatusBarButtonImage")) button.action = #selector(togglePopover(_:)) } popover.contentViewController = QuotesViewController.freshController() } 

Ha cambiado la acción de hacer clic para llamar al método togglePopover (_ :) , que escribiremos un poco más tarde. Además, en lugar de configurar y agregar un menú, configuramos una ventana emergente que mostrará algo de QuotesViewController .

Agregue los siguientes tres métodos a 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 () muestra una ventana emergente. Simplemente indica de dónde viene, macOS lo posiciona y dibuja una flecha, como si apareciera en la barra de menú.

closePopover () simplemente cierra la ventana emergente, y togglePopover () es un método que muestra u oculta la ventana emergente, dependiendo de su estado.

Inicie la aplicación y haga clic en su icono.



Todo está bien, pero ¿dónde está el contenido?

Implementamos Quote View Controller


Primero necesita un modelo para almacenar citas y atributos. Vaya al menú Archivo / Nuevo / Archivo ... y seleccione la plantilla macOS / Fuente / Archivo Swift , luego Siguiente . Asigne un nombre al archivo Cita y haga clic en Crear .

Abra el archivo Quote.swift y agregue el siguiente código:

 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)" } } 

Aquí definimos una estructura de cotización simple y una propiedad estática que devuelve todas las cotizaciones. Dado que hicimos que Quote cumpliera con el protocolo CustomStringConvertible , podemos obtener fácilmente un texto con formato conveniente.

Hay progreso, pero aún necesitamos controles para mostrar todo esto.

Agregar elementos de interfaz


Abra Main.storyboard y extraiga 3 botones ( Push Button ) y etiquete ( Multiline Label) en el controlador de vista.

Coloque los botones y la etiqueta para que se vean así:



Adjunte el botón izquierdo al borde izquierdo con un espacio de 20 y centre verticalmente.
Adjunte el botón derecho al borde derecho con un espacio de 20 y centre verticalmente.
Coloque el botón inferior en el borde inferior con un espacio de 20 y céntrelo horizontalmente.
Adjunte los bordes izquierdo y derecho de la marca a los botones con un espacio de 20, centrado verticalmente.



Verá varios errores de diseño, ya que no hay suficiente información para que el diseño automático pueda resolverlo.

Establezca la Prioridad de abrazo de contenido horizontal en 249 para permitir que la etiqueta cambie de tamaño.



Ahora haga lo siguiente:

  • establece la imagen del botón izquierdo en NSGoLeftTemplate y borra el título
  • establezca la imagen del botón derecho en NSGoRightTemplate y borre el título
  • configure el título del botón de abajo para Salir de las cotizaciones .
  • establece la alineación del texto de la etiqueta al centro.
  • verifique que el Salto de línea en la etiqueta esté configurado en Ajuste de línea .


Ahora abra QuotesViewController.swift y agregue el siguiente código a la implementación de la clase QuotesViewController :

 @IBOutlet var textLabel: NSTextField! 


Agregue esta extensión a la implementación de la clase. Ahora en QuotesViewController.swift hay dos extensiones de clase.

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

Acabamos de agregar una salida para la etiqueta que usaremos para mostrar las comillas, y 3 métodos de código auxiliar que conectaremos con los botones.

Conectando el código con Interface Builder


Nota: Xcode ha colocado círculos a la izquierda de su código, junto a las palabras clave IBAction e IBOutlet .



Los usaremos para conectar el código a la interfaz de usuario.

Mientras mantiene presionada la tecla alt , haga clic en Main.storyboard en el navegador de proyectos . Por lo tanto, el guión gráfico se abre en el Editor Asistente a la derecha y el código a la izquierda.

Arrastre el círculo a la izquierda de textLabel a la etiqueta en el generador de interfaces . Del mismo modo, combine los métodos anterior , siguiente y de salida con los botones izquierdo, derecho e inferior, respectivamente.



Inicia tu aplicación.



Utilizamos el tamaño emergente predeterminado. Si desea una ventana emergente más grande o más pequeña, simplemente cambie su tamaño en el guión gráfico .

Escribir un código para los botones


Si aún no ha ocultado el Editor Asistente , haga clic en Cmd-Return o V iew> Editor estándar> Mostrar editor estándar

Abra QuotesViewController.swift y agregue las siguientes propiedades a la implementación de la clase:

 let quotes = Quote.all var currentQuoteIndex: Int = 0 { didSet { updateQuote() } } 

La propiedad de citas contiene todas las citas, y currentQuoteIndex es el índice de la cita que se está mostrando actualmente. CurrentQuoteIndex también tiene un observador de propiedades para actualizar el contenido de la etiqueta con una nueva cita cuando cambia el índice.

Ahora agregue los siguientes métodos:

 override func viewDidLoad() { super.viewDidLoad() currentQuoteIndex = 0 } func updateQuote() { textLabel.stringValue = String(describing: quotes[currentQuoteIndex]) } 

Cuando se carga la vista, establecemos el índice de cotización en 0, lo que a su vez conduce a una actualización de la interfaz. updateQuote () simplemente actualiza la etiqueta de texto para mostrar una cotización. correspondiente currentQuoteIndex .

Finalmente, actualice estos métodos con el siguiente código:

 @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) } 

Los métodos next () y previous () recorren todas las citas. Salir cierra la aplicación.

Inicia la aplicación:



Monitoreo de eventos.


Hay una cosa más que los usuarios esperan de nuestra aplicación: ocultar la ventana emergente cuando el usuario hace clic fuera de ella. Para hacer esto, necesitamos un mecanismo llamado monitor de eventos globales macOS .

Cree un nuevo archivo Swift, llámelo EventMonitor y reemplace su contenido con el siguiente código:

 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 } } } 

Al inicializar una instancia de esta clase, le pasamos una máscara de evento que escucharemos (como pulsaciones de teclas, desplazamiento de la rueda del mouse, etc.) y un controlador de eventos.
Cuando estamos listos para comenzar a escuchar, start () llama a addGlobalMonitorForEventsMatchingMask (_: handler :) , que devuelve el objeto que estamos guardando. Tan pronto como ocurre el evento contenido en la máscara, el sistema llama a su controlador.

Para detener la supervisión de eventos, se llama a removeMonitor () en stop () y eliminamos el objeto configurándolo en nil.

Todo lo que nos queda es llamar a start () y stop () en el momento adecuado. La clase también llama a stop () en el desinfectante para limpiar.

Conectando el monitor de eventos


Abra AppDelegate.swift por última vez y agregue una nueva propiedad:

 var eventMonitor: EventMonitor? 

Luego agregue este código para configurar el monitor de eventos al final de applicationDidFinishLaunching (_ :)

 eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in if let strongSelf = self, strongSelf.popover.isShown { strongSelf.closePopover(sender: event) } } 

Esto informará a su aplicación cuando haga clic en el botón izquierdo o derecho. Tenga en cuenta que no se llamará al controlador en respuesta a los clics del mouse dentro de su aplicación. Es por eso que la ventana emergente no se cerrará mientras hace clic dentro de ella.

Utilizamos una referencia débil a uno mismo para evitar el peligro de un ciclo de fuertes vínculos entre AppDelegate y EventMonitor .

Agregue el siguiente código al final del método showPopover (_ :) :

 eventMonitor?.start() 

Aquí comenzamos a monitorear eventos cuando aparece una ventana emergente.

Ahora agregue el código al final del método closePopover (_ :) :

 eventMonitor?.stop() 

Aquí finalizamos el monitoreo cuando se cierra la ventana emergente.

¡La aplicación está lista!

Conclusión


Aquí encontrará el código completo para este proyecto.

Ha aprendido a configurar el menú y la ventana emergente en la aplicación ubicada en la barra de menú. ¿Por qué no experimentar con varias etiquetas o texto formateado para ver mejor las citas? ¿O conecta un backend para recibir cotizaciones de Internet? ¿O quieres usar el teclado para navegar entre comillas?

Un buen lugar para investigar es la documentación oficial: NSMenu , NSPopover y NSStatusItem .

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


All Articles