Aplicativo da barra de menus para o macOS

Os aplicativos localizados na barra de menus são conhecidos há muito tempo pelos usuários do macOS. Alguns desses aplicativos têm uma parte "normal", outros estão localizados apenas na barra de menus.
Neste guia, você escreverá um aplicativo que exibe várias citações de pessoas famosas em uma janela pop-up. No processo de criação deste aplicativo, você aprenderá:

  • atribuir ícone do aplicativo na barra de menus
  • torne o aplicativo hospedado apenas na barra de menus
  • adicionar menu personalizado
  • mostre uma janela pop-up a pedido do usuário e oculte-a quando necessário, usando o Monitoramento de Eventos

Nota: este guia pressupõe que você esteja familiarizado com o Swift e o macOS.

Introdução


Inicie o Xcode. Em seguida, no menu Arquivo / Novo / Projeto ... , selecione o modelo macOS / Application / Cocoa App e clique em Avançar .

Na próxima tela, insira Cotações como o Nome do produto , selecione o Nome da organização e o Identificador da organização . Em seguida, verifique se Swift está selecionado como idioma do aplicativo e a caixa de seleção Usar Storyboards está marcada. Desmarque as caixas de seleção Criar aplicativo baseado em documento , Usar dados principais , Incluir testes de unidade e Incluir testes de interface do usuário .



Por fim, clique em Avançar novamente, especifique o local para salvar o projeto e clique em Criar .
Depois que o novo projeto for criado, abra AppDelegate.swift e adicione a seguinte propriedade à classe:

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

Aqui, criamos na barra de menus Item de status (ícone do aplicativo) um comprimento fixo que ficará visível para os usuários.

Em seguida, precisamos atribuir nossa imagem a esse novo item na barra de menus para que possamos distinguir nosso novo aplicativo.

No navegador do projeto, acesse Assets.xcassets, faça upload de uma imagem e arraste-a para o catálogo de ativos.

Selecione uma imagem e abra o inspetor de atributos. Altere a opção Renderizar como para Imagem do modelo .



Se você usar sua própria imagem, verifique se a imagem é em preto e branco e configure-a como uma imagem de modelo para que o ícone fique ótimo nas barras de menu escura e clara.

Volte para AppDelegate.swift e adicione o seguinte código ao applicationDidFinishLaunching (_ :)

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

Aqui, atribuímos o ícone do aplicativo que acabamos de adicionar ao ícone do aplicativo e atribuímos uma ação quando clicamos nele.

Adicione o seguinte método à 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)") } 

Este método simplesmente imprime a cotação no console.

Preste atenção à diretiva do método objc . Isso permite que você use esse método como resposta a um clique no botão.

Crie e execute o aplicativo e você verá o novo aplicativo na barra de menus. Viva!
Cada vez que você clica no ícone na barra de menus, o famoso ditado de Mark Twain é exibido no console do Xcode.

Escondemos a janela principal e o ícone no banco dos réus


Há algumas pequenas coisas que precisamos fazer antes de lidar diretamente com a funcionalidade:

  • excluir ícone de encaixe
  • remover a janela principal do aplicativo desnecessário

Para remover o ícone da estação, abra Info.plist . Adicione uma nova chave Application is agent (UIElement) e defina seu valor como YES .



Agora é a hora de lidar com a janela principal do aplicativo.

  • abra Main.storyboard
  • selecione a cena Window Controller e exclua-a
  • Ver a cena da controladora sair, usaremos em breve




Crie e execute o aplicativo. Agora, o aplicativo não possui a janela principal e o ícone desnecessário no dock. Ótimo!

Adicionar menu ao item de status


Uma resposta de clique único claramente não é suficiente para um aplicativo sério. A maneira mais fácil de adicionar funcionalidade é adicionar um menu. Adicione esta função no final do 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 } 

E adicione essa chamada no final do applicationDidFinishLaunching (_ :)

 constructMenu() 

Criamos o NSMenu , adicionamos 3 instâncias do NSMenuItem a ele e definimos esse menu como o menu do ícone do aplicativo.

Alguns pontos importantes:

  • O título do item de menu é o texto que aparece no menu. Um bom lugar para localizar o aplicativo (se necessário).
  • ação , como a ação de um botão ou outro controle, é um método chamado quando o usuário clica em um item de menu
  • keyEquivalent é um atalho de teclado que você pode usar para selecionar um item de menu. Caracteres minúsculos usam Cmd como um modificador e caracteres minúsculos usam Cmd + Shift . Isso só funciona se o aplicativo estiver no topo e estiver ativo. No nosso caso, é necessário que o menu ou alguma outra janela esteja visível, pois nosso aplicativo não possui um ícone no dock
  • separatorItem é um item de menu inativo na forma de uma linha cinza entre outros elementos. Use-o para agrupar
  • printQuote é o método que você já definiu no AppDelegate e termina é o método definido pelo NSApplication .

Inicie o aplicativo e você verá um menu clicando no ícone do aplicativo.



Tente clicar no menu - selecionar Imprimir cotação exibe a cotação no console do Xcode e Quit Quote encerra o aplicativo.

Adicionar um pop-up


Você viu como é fácil adicionar um menu a partir do código, mas exibir uma cotação no console do Xcode claramente não é o que os usuários esperam do aplicativo. Agora vamos adicionar um controlador de exibição simples para exibir as cotações da maneira correta.

Vá para o menu Arquivo / Novo / Arquivo ... , selecione o modelo macOS / Source / Cocoa Class e clique em Avançar .



  • nomeie a classe QuotesViewController
  • tornar um herdeiro do NSViewController
  • verifique se a caixa de seleção Também criar arquivo XIB para a interface do usuário não está marcada
  • defina o idioma para Swift

Por fim, clique em Avançar novamente, selecione um local para salvar o arquivo e clique em Criar .
Agora abra Main.storyboard . Expanda Exibir cena do controlador e selecione Exibir instância do controlador .



Primeiro, selecione o Identity Inspector e altere a classe para QuotesViewController , depois defina o Storyboard ID como QuotesViewController

Agora adicione o seguinte código ao final do arquivo 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 } } 

O que está acontecendo aqui:

  1. nós temos um link para Main.storyboard .
  2. crie um identificador de cena que corresponda ao que acabamos de instalar logo acima.
  3. crie uma instância de QuotesViewController e retorne-a.

Como você cria esse método, agora todos que usam o QuotesViewController não precisam saber como ele é criado. Isso simplesmente funciona.

Observe o fatalError dentro da declaração de guarda . Pode ser bom usá-lo ou assertionFailure para que, se algo no desenvolvimento der errado, você e os outros membros da equipe de desenvolvimento fiquem sabendo.

Agora, de volta ao AppDelegate.swift . Adicione uma nova propriedade.

 let popover = NSPopover() 

Em seguida, substitua um pplicationDidFinishLaunching (_ :) pelo seguinte 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() } 

Você alterou a ação de clique para chamar o método togglePopover (_ :) , que escreveremos um pouco mais tarde. Além disso, em vez de configurar e adicionar um menu, configuramos uma janela pop-up que mostrará algo do QuotesViewController .

Adicione os três métodos a seguir ao 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 () mostra um pop-up. Você apenas indica de onde vem, o macOS posiciona-o e desenha uma seta, como se aparecesse na barra de menus.

closePopover () apenas fecha o pop-up e togglePopover () é um método que mostra ou oculta o pop-up, dependendo do seu estado.

Inicie o aplicativo e clique no ícone.



Está tudo bem, mas onde está o conteúdo?

Implementamos o Quote View Controller


Primeiro, você precisa de um modelo para armazenar cotações e atributos. Vá para o menu Arquivo / Novo / Arquivo ... e selecione o modelo macOS / Source / Swift File e , em seguida, Avançar . Nomeie o arquivo como Quote e clique em Create .

Abra o arquivo Quote.swift e adicione o seguinte 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)" } } 

Aqui, definimos uma estrutura de cotação simples e uma propriedade estática que retorna todas as cotações. Como tornamos o Quote compatível com o protocolo CustomStringConvertible , podemos facilmente obter texto formatado de maneira conveniente.

Há progresso, mas ainda precisamos de controles para exibir tudo isso.

Adicionar elementos da interface


Abra Main.storyboard e puxe 3 botões ( Push Button ) e a etiqueta ( Multiline Label) no controlador de exibição.

Posicione os botões e o rótulo para que eles se pareçam com isso:



Anexe o botão esquerdo à borda esquerda com um espaço de 20 e centralize verticalmente.
Anexe o botão direito à borda direita com um espaço de 20 e centralize verticalmente.
Anexe o botão inferior à borda inferior com um espaço de 20 e centralize horizontalmente.
Anexe as bordas esquerda e direita da marca aos botões com um espaço de 20, centralmente na vertical.



Você verá vários erros de layout, pois não há informações suficientes para o layout automático descobrir.

Defina a Prioridade de abraço de conteúdo horizontal como 249 para permitir que o rótulo seja redimensionado.



Agora faça o seguinte:

  • defina a imagem do botão esquerdo como NSGoLeftTemplate e limpe o título
  • defina a imagem do botão direito como NSGoRightTemplate e limpe o título
  • defina o título do botão abaixo como Quit Quotes .
  • defina o alinhamento do texto da etiqueta para centralizar.
  • verifique se a quebra de linha no rótulo está definida como quebra automática de linha .


Agora abra QuotesViewController.swift e adicione o seguinte código à implementação da classe QuotesViewController :

 @IBOutlet var textLabel: NSTextField! 


Adicione esta extensão à implementação da classe. Agora, em QuotesViewController.swift, existem duas extensões de classe.

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

Acabamos de adicionar uma saída para a etiqueta que usaremos para exibir aspas e três métodos de stub que conectaremos com os botões.

Conectando o código ao Interface Builder


Nota: O Xcode colocou círculos à esquerda do seu código - ao lado das palavras-chave IBAction e IBOutlet .



Vamos usá-los para conectar o código à interface do usuário.

Enquanto mantém pressionada a tecla alt , clique em Main.storyboard no navegador do projeto . Assim, o storyboard é aberto no Assistente do Editor à direita e o código à esquerda.

Arraste o círculo à esquerda do textLabel para o rótulo no construtor de interfaces . Da mesma forma, combine os métodos anterior , próximo e sair com os botões esquerdo, direito e inferior, respectivamente.



Inicie seu aplicativo.



Usamos o tamanho padrão do pop-up. Se você quiser um pop-up maior ou menor, redimensione-o no storyboard .

Escrevendo um código para os botões


Se você ainda não ocultou o Editor Assistente , clique em Cmd-Return ou View> Editor Padrão> Mostrar Editor Padrão

Abra QuotesViewController.swift e adicione as seguintes propriedades à implementação da classe:

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

A propriedade aspas contém todas as aspas, e currentQuoteIndex é o índice da cotação que está sendo exibida no momento. CurrentQuoteIndex também possui um observador de propriedades para atualizar o conteúdo do rótulo com uma nova cotação quando o índice for alterado.

Agora adicione os seguintes métodos:

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

Quando a visualização é carregada, definimos o índice de cotação como 0, o que, por sua vez, leva a uma atualização da interface. updateQuote () simplesmente atualiza o rótulo do texto para exibir uma cotação. currentQuoteIndex correspondente.

Por fim, atualize esses métodos com o seguinte 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) } 

Os métodos next () e previous () alternam entre todas as citações. sair fecha o aplicativo.

Inicie o aplicativo:



Monitoramento de eventos


Há mais uma coisa que os usuários esperam do nosso aplicativo: ocultar a janela pop-up quando o usuário clicar em algum lugar fora dela. Para fazer isso, precisamos de um mecanismo chamado macOS global event monitor .

Crie um novo arquivo Swift, chame-o de EventMonitor e substitua seu conteúdo pelo seguinte 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 } } } 

Ao inicializar uma instância dessa classe, passamos a ela uma máscara de evento que iremos ouvir (como pressionamentos de teclas, rolagens da roda do mouse etc.) e um manipulador de eventos.
Quando estivermos prontos para começar a ouvir, start () chama addGlobalMonitorForEventsMatchingMask (_: handler :) , que retorna o objeto que estamos salvando. Assim que o evento contido na máscara acontece, o sistema chama seu manipulador.

Para parar o monitoramento de eventos, removeMonitor () é chamado em stop () e excluímos o objeto, definindo -o como nulo.

Tudo o que resta para nós é chamar start () e stop () na hora certa. A classe também chama stop () no desinicializador para limpar.

Conectando o Monitor de Eventos


Abra AppDelegate.swift uma última vez e adicione uma nova propriedade:

 var eventMonitor: EventMonitor? 

Em seguida, inclua esse código para configurar o monitor de eventos no final do applicationDidFinishLaunching (_ :)

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

Isso informará seu aplicativo quando você clicar no botão esquerdo ou direito. Observe: o manipulador não será chamado em resposta aos cliques do mouse dentro do seu aplicativo. É por isso que o pop-up não fecha enquanto você clica dentro dele.

Usamos uma referência fraca a si próprio para evitar o perigo de um ciclo de vínculos fortes entre AppDelegate e EventMonitor .

Adicione o seguinte código no final do método showPopover (_ :) :

 eventMonitor?.start() 

Aqui começamos a monitorar eventos quando uma janela pop-up aparece.

Agora adicione o código no final do método closePopover (_ :) :

 eventMonitor?.stop() 

Aqui terminamos o monitoramento quando o pop-up é fechado.

A aplicação está pronta!

Conclusão


Aqui você encontrará o código completo para este projeto.

Você aprendeu como definir o menu e pop-up no aplicativo localizado na barra de menus. Por que não experimentar várias tags ou texto formatado para obter uma melhor aparência das aspas? Ou conectar um back-end para receber cotações da Internet? Ou você deseja usar o teclado para navegar entre aspas?

Um bom lugar para pesquisar é a documentação oficial: NSMenu , NSPopover e NSStatusItem .

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


All Articles