July 5, 2011

Cocoa Popup window in the Status bar

My CodeBox app for Mac OS X has got an embedded helper app named CodeBar. It is a small utility resided in the system status bar. In this blog post, I'm going to share an open-source project demonstrating how to implement a custom status icon with a popup window. I hope it will be useful for those who are new to Cocoa and want to make their own utility living in the status bar.

First, we have to allocate a new NSStatusItem to place it into the menu bar. This task is managed by the controller class named MenubarController. To make status item more flexible, we use a custom StatusItemView to display its contents in the menu bar. The demo project provides the simplest implementation that draws a Star icon in that view. You can have even more advanced icon by adding handling of drag & drop or any other feature supported by regular views.

Next, we need a customized window to display it as a popover. In the demo project, this window is a panel managed by the PanelController class. It loads the panel component from NIB and manages its live cycle. Also, it configures popover appearance by removing title bar and standard window background.

Finally, once we have menu bar icon and popover, it is time to connect them. The top level controller class ApplicationDelegate is watching for clicks in the menu bar and passes them to the panel controller. Then, after panel controller is closed, application delegate asks menu bar icon to remove a blue highlight.

Popup project on GitHub has got all source files that you need to build a demo app in Xcode 4.

P. S. In Lion, Apple is adding a new class for popovers like in iOS. So, after OS X 10.7 is released, you would better to rely on native Cocoa classes where it is possible. In other cases, the Popup project should still be usable.

Update: What a shame. It looks like implementation using NSPopover requires a lot of hacks. Please find more information here.

40 comments:

  1. the image for the popup is broken in this post.

    ReplyDelete
  2. Great example, very well coded. Thanks Vadim.

    ReplyDelete
  3. Really nice. Thanks for sharing.

    ReplyDelete
  4. Thank Dude, this is a V-Nice post; much appreciated.

    ReplyDelete
  5. How can do to make the window move from position?

    Great post.

    ReplyDelete
  6. @Anonymous Could you please be more specific? What do you mean by “window move”? Thanks.

    ReplyDelete
  7. Nice example. But can you tell me please, what does

    void *kContextActivePanel = &kContextActivePanel;

    do?

    ReplyDelete
  8. void *kContextActivePanel = &kContextActivePanel is another way to make a unique constant identifier kContextActivePanel for using in the key value observing.

    Something like void *kContextActivePanel = @"ActivePanelContext" would work too.

    ReplyDelete
  9. I'm wondering if there's any way you'd update your code to use the new NSPopOver class? (And, ARC/xcode 4.2 would be nice too ;) )

    ReplyDelete
    Replies
    1. Maybe someday, when I have time :)

      Delete
    2. Please do that :) If I try to code this it always won't work :(

      Delete
    3. Or can you please make a video tutorial?
      I think when you show us how to do that, it's easier to understand what we have to do.

      Delete
    4. Please update for using ARC.

      Also,

      How can you add text next to the image in the menu bar?

      Delete
    5. I understand the problem of time, but a step-by-step video tutorial with explanations would be a tremendous help for beginners!

      Delete
  10. Vadim,

    I test your code, but I notice that when you open the panel, and don't click outside the window (panel), and click in other icon on the menubar, the panel don't close.

    Do you know how to fix this?

    Thanks.

    ReplyDelete
    Replies
    1. Thanks for the heads up, I have just fixed it!

      Delete
    2. Without being annoying, on the first click the panel still does not close if you click on another icon from the menubar.

      After you click out of the menu bar for a first time, it works correctly every time than, but before that ​​the panel remains open, following a screenshot:

      http://img826.imageshack.us/img826/8528/popupx.png

      Delete
    3. That's because you run the app from Xcode. Try to launch it from Finder.

      Delete
    4. You are right. I did not know this, thanks.

      Delete
  11. Please show us how it works

    ReplyDelete
  12. I have tried this in my own app, on Lion it all works fine.
    However, there is an error, when the PanelController is calling it's window.
    It seems to be a zombie. I use ARC btw.
    I have no idea why this happens, when I convert the PanelProject right here to ARC, it works flawlessly.
    Can anyone help me out?

    ReplyDelete
    Replies
    1. Please create a new issue on the GitHub and describe step-by-step how to reproduce it. Thanks in advance!

      Delete
  13. Maybe you can advise how to include resize in your "popup" ? I founded solution to set window stylemask to NSResizableWindowMask, but i still can't hide title bar.(

    ReplyDelete
    Replies
    1. I believe you cannot use a resizing behavior of a window because it is handled by the theme frame which requires a title bar. Sorry.

      Delete
    2. i did it) with custom resize rect and mouse event.

      Delete
    3. Congratulations, it must have been quite a tricky task :)

      Delete
  14. anonymous june 16: it would be nice if you could share how you did the windows resize.

    Vadim: thanks for sharing a great tutorial.

    ReplyDelete
  15. For anyone using MonoMac, I've written a port here: http://dan.clarke.name/2012/08/cocoa-popup-window-in-the-status-bar-monomac-port/

    Many thanks to Vadim for the initial solution!

    ReplyDelete
  16. Hey guys, this is an awesome template and I have been using it in two of my projects now!

    Just wanted to know, if i want display the app on left click, and I want to display a menu on right click, what do I do?

    ReplyDelete
    Replies
    1. Thanks for the feedback! I would try to override mouseDown: in StatusItemView.m and send different actions depending on the pressed mouse button. Sorry for the late reply :)

      Delete
  17. Im trying to run it on MAC OS X 10.6 with Xcode 4
    Although it is working fine on MAC OS 10.8 but having problem when I use it on MAC OS X 10.6. It's not running.. ?

    ReplyDelete
    Replies
    1. I have no 10.6 installed to test. Could you please be more specific? What kind of problem do you have exactly? Sorry for inconvenience.

      Delete
  18. Awesome stuff! It's great that you are still maintaining it after 2 years now! Kudos!

    ReplyDelete
  19. hi, your project helped me a lot in understanding how things are working!

    I'm trying to understand how to close the panel only by clicking on status bar icon?
    My app opens some "choose file" dialogs, so i have to click each time to reopen the panel..

    hints?

    ReplyDelete
  20. This is a great example of exactly what I was looking for. However, there was a question awhile back that asked something I wanted to know also:

    "How can you add text next to the image in the menu bar?"

    Can you please respond to this?

    Thanks!

    ReplyDelete
  21. Wasn't sure how to thank you for this, so I bought CodeBox. This can be my first snippet to save, thank you, thank you, thank you :)

    ReplyDelete
  22. Its a great code, however can you customize it load webview?

    ReplyDelete