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
SÍ .

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
QuotesViewControllerAhora agregue el siguiente código al final del archivo
QuotesViewController.swift :
extension QuotesViewController {
¿Qué está pasando aquí?
- obtenemos un enlace a Main.storyboard .
- cree un identificador de escena que coincida con el que acabamos de instalar justo arriba.
- 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.
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ándarAbra
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 .