Kodeco Forums

OS X Tutorial: Menus and Popovers in Menu Bar Apps

Learn how to make an OS X menu bar app with menu items or a custom view controller popover in this OS X tutorial!


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1843-os-x-tutorial-menus-and-popovers-in-menu-bar-apps

Thank you very much for your tutorial.
It works on on app.
Now I would like to add the Log In View (for authentication) before launching the app, and I need to add the feature that the app would be launched automatically after the system log in.

Would you please give me the direction on how to do the Log In View and the auto start after system log in?

Thank you very much for your help.

Sincerely,
Joseph

You are welcome. I’m glad the tutorial was helpful.

For creating a new login view, you would need to create a window, you can use the one in MainMenu.xib, you hide it during the tutorial, but you could unhide it and make it you login screen, then hide it programmatically if the user is already logged in.

For making the app launch at startup:

  1. Make a new Mac app called Helper
  2. Put this in your main app’s bundle under Contents/Library/LoginItems
  3. Use SMLoginItemSetEnabled to launch that helper app at login.
  4. Make your helper app launch the main app immediately at startup.

This sounds a bit like a mess, but it is the recommended way of doing this by Apple.

1 Like

Thanks so much for this tutorial, I found it really useful when working on our first Mac app. I’m getting a leak of EventMonitor when building and running as a Swift 3 project. It’s small (48 bytes), so not a big deal, but just checking to see if there is anything I need to change from what you’ve already shared to prevent this leak?

I downloaded the completed project and updated it to Swift 3 Syntax using the default settings and suggestions (here is the project) and the Memory Graph and Instruments report a leak on Event Monitor on launch. Updating to Swift 2.3 does not appear to have the same leak.

I’m guessing the problem is here:

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

Any suggestions on how I could plug this leak?

In applicationDidFinishLaunching, eventMonitor?.start() is called, which creates a new eventMonitor, this is then called again in showPopover(); this global monitor is never removed.

Is there a way to make the app shortcuts available globally?
What i mean is, like in the wunderlist app, in which you can simply press ctrl+alt+w anywhere in your mac to open a modal window of the app?
Thank you

Can the highlighting behavior of Custom NSMenuItem (using NSView with allowVibrancy - true) be replicated. I used NSColor.selectedMenuItemColor, but it is darker than the original one. Would you please give me the direction on how can I do this.
Thank You

i followed this tutorial, but the icon never showed in my app, if i open your final projet and updates it to swift 3.0 it works.

the only diffrens between my app and yours, is that i want the icon in the top all the time like twitter mac app and not hide the doc icon. here is my code , hope you can help me

import Cocoa


@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

 lazy var coreDataManager = CoreDataManager()


let statusItem = NSStatusBar.system().statusItem(withLength: -2)
let popover = NSPopover()
var eventMonitor: EventMonitor?

func applicationDidFinishLaunching(_ aNotification: Notification) {
    
    if let button = statusItem.button {
        button.image = NSImage(named: "StatusBarButtonImage")
        button.action = #selector(AppDelegate.togglePopover(_:))
    }
    
        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        popover.contentViewController = storyboard.instantiateController(withIdentifier: "PopoverViewController") as! PopoverViewController
  
    eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [unowned self] event in

        if self.popover.isShown {
          self.closePopover(event)
        }
}
eventMonitor?.start()
}


func applicationWillTerminate(_ aNotification: Notification) {
    // Insert code here to tear down your application
    coreDataManager.saveContext()
}


func togglePopover(_ sender: AnyObject?) {
    if popover.isShown {
        closePopover(sender)
    } else {
        showPopover(sender)
    }
}

func showPopover(_ sender: AnyObject?) {
    if let button = statusItem.button {
        popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
    }
    eventMonitor?.start()
}

func closePopover(_ sender: AnyObject?) {
    popover.performClose(sender)
    eventMonitor?.stop()
}
}

Hello, definitely a great tutorial. Thank you very much for sharing it. I am new to Swfit and XCode and OSX programming, so this tutorial is a great resource to learn.

I am using XCode 8.2.1 and Swift 3, I have found myself with a lot of errors when using the Selector, the first one I found was Selector("terminate:") it shows an error related to the API, also recommend using #selector, so after searching around and reading a bit I came up with a solution that works, I would like to kindly ask you to review it and let me know if this is the correct way to do it, as I am new to this.

//
//  AppDelegate.swift

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
    
    let statusItem = NSStatusBar.system().statusItem(withLength: -2)
    let popover = NSPopover()

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        if let button = statusItem.button {
            button.image = NSImage(named: "StatusBarButtonImage")
            button.action = Selector(("printQuote:"))
        }
        let menu = NSMenu()
        
        menu.addItem(NSMenuItem(title: "Print Quote", action: #selector(AppDelegate.printQuote(sender:)), keyEquivalent: "P"))
        menu.addItem(NSMenuItem.separator())
        menu.addItem(NSMenuItem(title: "Quit Quotes", action: #selector(AppDelegate.quit(sender:)), keyEquivalent: "q"))
        
        statusItem.menu = menu
        // Insert code here to initialize your application
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
    
    func printQuote(sender: NSMenuItem) {
        let quoteText = "Never put off until tomorrow what you can do the day after tomorrow."
        let quoteAuthor = "Mark Twain"
        
        print("\(quoteText) — \(quoteAuthor)")
    }
    
    func quit(sender: NSMenuItem) {
        NSApp.terminate(self)
    }
}

Thank you very much and please keep posting this is great!

i figured this out, my app uses a tabbar as main view, so i moved the NSStatusbar to the frist viewcontroller in the tabbar instead of the AppDelegate. :slight_smile:

Seems like the finished will no longer complied. Even after converted. Could you please update it? Many thanks!

Hi, I wonder if you have thought about updating this tutorial for Xcode 8.3.3? In following the above instrux I get “Use of unresolved identifier ‘println’” early on in the tutorial…
I’d love to get this working as a toe-dip into Xcoding.
Many thanks.

Thank you so much for this Tutorial!
Just started with Swift few days ago (or XCode in general) and learned so much from your Website.

This Project was kind of challenging since I had to do a lot of stuff a different way. I programmed the whole thing in Swift 4 on XCode 9, and made everything in a storyboard instead of a .xib. So I had to figure things out with the help of the internet and the Swift API Reference.

Furthermore I removed the Quit Button and integrated a menu when you right click the status bar icon. There you can find preferences for Light and Dark UI (which is not yet functional) and exit the Application. Next on my list is adding a button for sharing/copying the current quote.

Also I wanna try the suggestion with the web backend after the app is so far complete. If someone can recommend sources where i can educate myself on doing stuff like that, it would be great :slight_smile: Since I have absolutely no idea of how to pull that off.

Leave it to Apple to make something so messy and then force you to use that. It really shouldn’t be this complicated for a feature that the vast majority of apps today have. You should really make a tutorial about this, because there are a lot of conflicting ways to do it out there, and people are struggling with it. I have too much time looking for an updated tutorial on how to do this using Xcode 9 and Swift 4, but no luck yet.