SwiftUI: How to use NSSearchToolBarItem on macOS 11?

1.1k views Asked by At

I watched the WWDC video "Adopt the new look of macOS" where they introduced NSSearchToolBarItem in Swift at 11:32 in the video.

This is exactly what I need to do but how do I add this search to my toolbar in SwiftUI?

There are lots of complex examples but they all seem to be written before this new API. There must be a simpler way?

import SwiftUI

var listItems = ["Item 1", "Item 2", "Item 3", "Item 4"]

struct ContentView: View
{
    
    @State var select: String? = "Item 1"
    @State var searchText: String = "hello"

    var body: some View
    {
        VStack
        {
            NavigationView
            {
                List
                {
                    ForEach((0..<listItems.count), id: \.self)
                    {index in
                         Label(listItems[index], systemImage: "briefcase")
                    }
                }
            }
            
            .toolbar
            {
                Button(action: {})
                {
                    Label("Upload", systemImage: "square.and.pencil")
                }
                TextField("Search", text: $searchText)
            }
        }
    }
}
1

There are 1 answers

0
Jason Barrie Morley On

There doesn't look to be an obvious way to do this using NSSearchToolbarItem directly but, looking into that class, it's using an NSSearchField under the hood which is relatively easy to use in SwiftUI using NSViewRepresentable:

struct SearchField: NSViewRepresentable {

    class Coordinator: NSObject, NSSearchFieldDelegate {
        var parent: SearchField

        init(_ parent: SearchField) {
            self.parent = parent
        }

        func controlTextDidChange(_ notification: Notification) {
            guard let searchField = notification.object as? NSSearchField else {
                print("Unexpected control in update notification")
                return
            }
            self.parent.search = searchField.stringValue
        }

    }

    @Binding var search: String

    func makeNSView(context: Context) -> NSSearchField {
        NSSearchField(frame: .zero)
    }

    func updateNSView(_ searchField: NSSearchField, context: Context) {
        searchField.stringValue = search
        searchField.delegate = context.coordinator
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }

}

You can then use this hosted view directly inside a ToolbarItem:

ToolbarItem {
    SearchField(search: $filter)
        .frame(minWidth: 100, idealWidth: 200, maxWidth: .infinity)
}

You may wish to fiddle with the dimensions a little (in the .frame() call) to match the exit platform-native dimensions but this seems to match pretty closely how Notes.app looks.

Search toolbar item in Notes.app:

Screenshot showing search toolbar item in Notes.app

Search toolbar item from the example code above:

Screenshot showing search toolbar from example code