Kodeco Forums

Menus and Popovers in Menu Bar Apps for macOS

In this Menu Bar App tutorial you will learn how to present a menu and a popover that shows quotes from famous people.


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

I have an app thats main purpose has windows and a dock icon. I also want it to display a menubar icon with menu. I have set this up, but the menubar menu cannot be seen over a fullscreen app unless I set the “Application is agent (UIElement)” key in info.plist. When I set this, the popover works correctly and displays over a full screen safari or mail client. But now my app has lost dock functionality and no longer has visible menu options.

This might sound crazy, but I would like to have a working app that ALSO has a working menubar menu. What is required to make this work?

Hi

Im not sure what is causing your display issues. I checked the tutorial code and it will display a menu or popover over a full screened app regardless of whether LSUIElement is set to true or false in the plist.

The effect of setting LSUIElement to true is making the app “Dockless”. It wont have any effect on whether the menubar item works. Possibly something in your window configuration or app configuration is doing this.

I had a bug in one of my apps that was caused by not returning false from func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool. Its probably not the same thing as you but more advice to check around what else could be causing this effect.

Thanks for putting this tutorial together. I’m trying to run through it using Xcode 8.3.3 and having some issues. When I setup the status item in one of the very first steps and run the app the status item appears for a split second in the status bar but then disappears. Any ideas why that may be happening? I cannot go further with the tutorial as there is nothing to click on! =(

Found the link to the project zip and figured out the issue. I had the declaration of the status item inside applicationDidFinishLaunching. Moved it out as a class property and it no longer disappears. =)

Hello,

Thank for this tutorial !
It’s possible to hidden one menu, and when we press Option key + Clic on the menu it will appeare ?

This has bugs in dual monitor environment and mission control

Assume the popover was open on your primary monitor, and you immediately closed it by clicking on the secondary monitor. Then your mouse click occurred on the primary monitor again.

The popover window would not show up on the primary monitor no matter how many times you click the status icon until you click it back on the secondary monitor.
And vise versa

As for Mission Control, it gets messy because the popover stays to dominate over Mission Control screen.

@warrenburton Do you have any feedback regarding this? Thank you - much appreciated! :]

Hi Kyle. Sorry for the late reply. I have been travelling.

The tutorial projects tend to be kept fairly simple to focus on the basic aspects of a given API so the slightly more complex cases that you raise here are not dealt with. Nice bug finding :]

For the first case of trapping the second/other screen click, looking at the tutorial code we dismiss the popover and stop the event monitor. I’m wondering if the togglePopover or closePopover code needs to be a bit more clever. I dont have a dual display right now so cant check. That would be my first instinct here.

For the second case of not vanishing when Mission Control comes up thats more complex as there doesnt seem to be a easily public way of doing this. This SO answer has an approach and I see that Dropbox gets it right so it must be possible.

Thanks for bringing up these issues. Ill have a poke around and see if i can work out an answer. Ill be back on my dual display in a few days so can test that out too.

Hi. Sorry for the delayed reply. I have been away.

Thanks for the good question

You need to add an additional EventMonitor object to listen for the option key being held down and update the menu var on your status item on that basis.

It’ll look something like…

optionKeyMonitor = EventMonitor(mask: [.flagsChanged]) { [weak self] event in
      if let _ = self, let event = event {
        if event.modifierFlags.contains(.option) {
          //set alternate menu
        }
        else {
          //set normal menu
        }
      }
    }

I haven’t tested that but it should lead you in the right direction.

I just want to say a big thanks! This tutorial explained a lot and helped me understand some basic Xcode/Swift concepts.
Do the books you guys sell have more tutorials like this in them? And are they even more advanced?

@jdowdy6 The books on the website are tutorial based and dive into advanced topics. Please let me know what kind of stuff are you interested in. Thank you! :]

Okay great, definitely thinking of getting one with Christmas cash. Does the online subscription ($20/month) include access to the books, or only video tutorials?
As far as what I’m interested in, I’m really interested in your 3D Apple Games book and I’m also interested in macos apps. It seems like most of the books focus on iOS? I’ve gone through a few awesome macos tutorials you guys have, but it doesn’t seem like there’s a book focusing solely on it, everyone is probably interested in iOS right now.

The online subscription includes video screencasts and courses only. We are working on more macOS related products, so stay tuned! :]

Hi guys,

Sorry if the answer to this question is obvious, but when I do the first step in the tutorial (add the first property to the class), I just get an error no mather what I do, so when I come to the part where I’m supposed to test run the app, I can’t. The error is the following:

"Method 'system' was used as a property, add () to call it"

But when I do the fix and att () to system, I get the following error instead:

"Type 'NSStatusItem' has no member 'squareLength'"

Am I indeed missing something obvious here?

Check your Xcode version & Swift version are the latest ones. - squareLength was only correctly defined later on. Previous you had to hack it with a const CGFloat value. Also having to use system() as opposed to system indicates you’re not using the 10.12 + Swift 3.? toolchain.

You’re right, I updated all of it (really thought I already had so didn’t check), works fine now. Thanks for the help!

Hi, May I have your attention, I have followed your tutorials and running successfully,
And, I want add a feature, when the cursor in the statusItem area, the popover shows,
when the cursor off the statustItem area, the popover close.
thanks a lot!!

Thats an interesting idea but tricky. You get read access to the NSButton instance that NSStatusItem vends but you cant supply your own one.

If I was trying to do this I might install a NSView subclass as a subview of that button (that completely covers the button) that handles mouseEntered(with: NSEvent) and mouseExited(with:NSEvent) . Both are methods of NSResponder that NSView inherits from.

If I remember right you also need to install a tracking rect for those two calls to work. Use

NSView.addTrackingRect(_ rect: NSRect, owner: Any, userData data: UnsafeMutableRawPointer?, assumeInside flag: Bool) -> NSView.TrackingRectTag

That may or may not work depending on how the NSStatusItem persists the button instance or if theres some recycling going on.

Good luck and thanks for reading.

Great tutorial, thanks!

I chose to put text instead of an icon on the statusItem. However, this text is placed lower than other menu items and the clock.

It is possible to shift everything using button.setFrameOrigin(NSPoint(x: 0, y: 1)), but then the whole button is shifted up, including the menu.

I would like to shift the text label only. Would that involve creating a custom view? I am new to Mac/Swift development and it is a bit hard to find the right answers as I don’t know all the exact nomenclature. But this website is great! Can you point me into the right direction?

Thanks!
Mark