How to add a Search Bar to a macCatalyst application

Deniz Nessa
eclypse blog
Published in
6 min readSep 2, 2023

--

We got inspired by a StackOverflow question and the accepted answer no longer seemed to have worked. At the time of the question the OP was puzzled that there was not a way to add a Search Bar to a mac catalyst toolbar. Apple’s SDK simply did not provide it. You could only create basic toolbar items with an image and a title. Almost 4 years later, this changed somewhat; however, we still decided to take on the challenge and see it for ourselves. This blog shows how to embed NSSearchBar from AppKit into a macCatalyst application and make it look nice and functional.

Example app built with macCatalyst. Source: apple.com

Option 1: Try UISearchBar

As we mentioned earlier there have been some changes in the SDK. As of macOS 13, you can now embed any UIView in a Toolbaritem. This UIView can also be UISearchBar. So in your toolbar delegate class something like this can be achieved as long as you are targeting macOS 13 or later.

func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
var toolbarItemToInsert: NSToolbarItem?
switch itemIdentifier {
case .uiSearchBar: //assumes that you have NSToolbarItem.Identifier.uiSearchBar defined earlier
let uiSearchBar = UISearchBar()
uiSearchBar.backgroundImage = UIImage() //an empty image removes the default background color
uiSearchBar.placeholder = "Search"
uiSearchBar.delegate = self //or whichever object most appropriate

// using custom views is not supported when targeting macOS 12 or earlier
// see https://developer.apple.com/documentation/appkit/nstoolbaritem/3375792-init
let uibarButtonItem = UIBarButtonItem(customView: uiSearchBar)
toolbarItemToInsert = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: uibarButtonItem)
...
... //configure other items in your toolbar
...
return toolbarItemToInsert
}

Result:

UISearchBar looks out of place when embedded in NSToolbar

UISearchBar works reasonably well. However there are 2 main drawbacks.

  1. If your catalyst app can run on macOS devices that are earlier than macOS 13 this solution will not work. If you try to run on earlier devices, you see an empty spot where the search bar used to be.
  2. There is really no way to size the UISearchBar. It takes up the entire available width in the toolbar no matter what we tried. Its height is also not correct and does not quite align with other items on the toolbar. Share button is placed right next to the search bar for reference purposes.

If these 2 issues are not a showstopper for your application, I think this option may be the right one for your project.

Option 2: Embed NSSearchBar from AppKit

This option looks the best and works on earlier macOS versions but it is a bit more involved. Good news is that we will show you how to do this step by step, so it probably won’t take much time to integrate. Before we dive any further, let’s see how it looks compared to Option 1.

NSSearchBar looks more familiar and is correctly sized

If you liked this presentation better then let’s get to work…

Step 1: Add macOS Framework to your project

Name your new framework whatever you want. However, for this example, we named it macOSBridging. This name will be important later on.

Once the framework is added, your targets should look like this:

Step 2: Configure Your App’s Build Settings

We need to make a few changes in your main target’s build settings.

a. Make sure your main target is selected
b. Go to General tab
c. Add the framework we created in Step 1
d. Make sure the filter value is Mac Catalyst
e. Embed & Sign option is selected

f. Go to Build Phases tab
g. Remove the framework from “Link Binary with Libraries” section

h. Select framework target
i. Select General tab
j. Make sure minimum macOS deployment version is appropriate for your primary target. Ideally you should chose the value that corresponds to your iOS target.

Step 3: Copy files from the example project

Head over to the Github repository where the example project is hosted. Download the 5 files and add them to your macOS framework as shown in the screenshot below:

Since the primary purpose of this blog is catalyst integration, we will not go into AppKit’s internal workings and how NSSearchBar is configured. Once you have the macOS search bar up and running, you can change the contents of those files according to your needs.

Step 4: Load the macOS framework in Toolbar delegate

In your toolbar delegate add the following code:

func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
var toolbarItemToInsert: NSToolbarItem?
switch itemIdentifier {
case .nsSearchBar: //assumes that you have NSToolbarItem.Identifier.nsSearchBar defined earlier
if let frameworksPath = Bundle.main.privateFrameworksPath {
let bundlePath = "\(frameworksPath)/macOSBridging.framework" //make sure the name you given earlier matches here exactly
do {
//we want to check whether we can load this dependent framework
try Bundle(path: bundlePath)?.loadAndReturnError()
_ = Bundle(path: bundlePath)! //at this point, we can load the framework; therefore, safe to unwrap

//we have to use some Objective-C trickery to load the SearchbarToolItem from AppKit
if let searchbarToolItemClass = NSClassFromString("macOSBridging.SearchbarToolItem") as? NSToolbarItem.Type {
let newItemToAdd = searchbarToolItemClass.init(itemIdentifier: itemIdentifier)
newItemToAdd.isBordered = false
toolbarItemToInsert = newItemToAdd
print("Successfully loaded NSSearchBar into our toolbar!")
} else {
print("There is no class with the name SearchbarToolItem in macOSBridging.framework - make sure you spelled the name correctly")
}
} catch {
print("error while loading the local framework: \(error.localizedDescription)")
}
}
...
... //configure other items in your toolbar
...
return toolbarItemToInsert
}

Also don’t forget to add this item identifier to the default list in your Toolbar delegate:

func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [..., .nsSearchBar]
}

Step 5: Setup communication

NSSearchBar in the macOS framework broadcasts a notification every time user types into the search bar. There are other ways to relay the message like using the responder chain; however, for the sake of this blog, we decided to use this method. Define an enum as shown below in your app.

enum InternalNotification: String {
// the string value of this notification has to match the raw value of the notification in macOSBridging.InternalNotification
// if you change the string value here, you need to go to macOSBridging.InternalNotification class and enter the same value there as well
case toolbarSearchBarTextChanged = "topDomain.companyName.app.notification.searchbartextchanged"

var name: Notification.Name {
return Notification.Name(rawValue: self.rawValue)
}
}

Last Step: Listen for text changed notifications

Then in your view controller or anywhere else that should receive this search bar text changed event should observe the notifications. The following code example assumes that a view controller is listening:

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
...
...
NotificationCenter.default.addObserver(self, selector: #selector(userPerformedSearch(_:)), name: InternalNotification.toolbarSearchBarTextChanged.name, object: nil)
//removing observers are optional now.
//see https://developer.apple.com/documentation/foundation/notificationcenter/1415360-addobserver#discussion
}

...
...
func userPerformedSearch(_ notification: NSNotification) {
//NSNotification.object contains the typed text in NSSearchBar
if let searchedText = notification.object as? String {
//do something with the search text
}
}

Conclusion and Example Project

We hope that you found this guide useful. If you want to see a working example, a github repository is created here. Feel free to clone the repository and inspect the code. If you have suggestions or improvements, you may open issues in the github project. Here is a preview of the example app with NSSearchBar integration:

--

--