A Safari-like Search Field Control

| February 15th, 2010

Last weekend I went looking for a way to duplicate the functionality of Safari’s search field. This began an epic journey that has nearly killed me…hopefully it’s true what they say and I’m stronger for it.Before I go much further, I should mention that someone was kind enough to point out an implementation of something similar to what I want at Summer of Camino 2009. I have little doubt that it would’ve been a lot simpler to use someone else’s (probably superior) implementation. Unfortunately it looks like it’s GPL and while I have no problem sharing my code, I’ll be using it in a closed source application. So off I went in an attempt to build my own.

Did I mention I’ve never done a custom control before? Some drawing in views, sure, but never a control. Let alone one with a weird menu that stays open while you type in a text field. I imagined it would be a learning experience, and I definitely learned a lot about being frustrated (my knowledge of making a custom control is still iffy).

The main functionality I was looking for from Safari’s search field was:

  • menu includes search matches (Suggestions) as well as recent searches
  • menu opens when the user enters text that has possible matches (rather than just when the button is clicked)
  • menu will update the matches while the user continues to type
  • change focus from the text view to the menu and back with the arrow keys

I started out by snooping around in Safari using F-Script. It’s using subclasses of NSSearchField and NSSearchFieldCell to do it’s job, so I figured that would be a good thing to try out first. Unfortunately, I just couldn’t figure out how to keep the menu open. After a bit of fruitless hacking to try an mimic Safari, I decided to give up on subclassing NSSearchField and start instead with NSTextField. NSSearchField just lets you provide a template menu and then at some point it uses that menu to build a new one with the placeholders fixed up with recent searches, etc. It’s a fine concept, but I thought it was getting in the way of my understanding on how to keep the stupid menu open.

Still I couldn’t figure out how so make menu open. Finally, I started making some progress when I gave up on using a menu at all and decided to fake it with a child window. I can’t be sure, but my suspicion is that Safari is doing the same thing (my F-Script skills aren’t advanced enough to figure it out).

Once I had the menu problem solved, my only barrier was my complete lack of control programming knowledge. I forged on fearlessly (or foolishly) ahead and got something that basically work. So am I happy with the results? Not really. I had enough issues in dealing with managing the control’s focus that I feel I just don’t get it yet and there should be better ways to do it. I know part of the problem is the funkiness of NSTextField and the associated field editor and how the first responder is set between the two. I’m also coming to somewhat regret not sticking with subclassing NSSearchField and NSSearchFieldCell since I had some issues drawing the control with the search icon inside the text field’s border.

In the end though, it works well enough for me (for now). I’m still planning on revisiting it at some point to try and clean up the messiness but right now I don’t think I’m in the right mind to look at it. There are also definitely some limitations that I don’t feel affect me (yet). Since the menu is completely fake, you don’t get much flexibility from it. You can’t have submenus or view-based menu items for instance. Also, there’s no asynchronous behavior in populating the menu. If, for some reason, the BRSearchFieldDataSource needs a long time to produce a set of results, there’s no built in support for handling that.

For the brave, insane, and those who wish to mock me, the source is available on github here: BRSearchField. Using it isn’t too bad. The only non-straightforward part that isn’t illustrated by the code is that you add an NSTextField in Interface Builder, then must change it’s class to BRSearchField and it’s cell’s class to BRSearchFieldCell. Other than that, set it’s data source and search menu prototype either in IB or in code and you should be (hopefully) good to go.

Leave a Reply