Replicating the MacOS Search TextField in SwiftUI
Tags: swift, swiftui, macos
Published: 2020-03-22 15:32:11

I wanted to add a search text field to a MacOS app that I'm working on and soon discovered it's not available in SwiftUI (as off 5.1). I've put together a quick replication which mostly works, but unfortunately involves some hackiness.

![Preview of Search TextFIeld](https://dev-to-uploads.s3.amazonaws.com/i/uje3uwqxxogogpex9d5e.gif)

Here's the code:

```swift
// This extension removes the focus ring entirely.
extension NSTextField {
    open override var focusRingType: NSFocusRingType {
        get { .none }
        set { }
    }
}

struct SearchTextField: View {
    @Binding var query: String
    @State var isFocused: Bool = false
    var placeholder: String = "Search..."
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 5, style: .continuous)
            .fill(Color.white)
            .frame(width: 200, height: 22)
                .overlay(
                    RoundedRectangle(cornerRadius: 5, style: .continuous)
                        .stroke(isFocused ? Color.blue.opacity(0.7) : Color.gray.opacity(0.4), lineWidth: isFocused ? 3 : 1)
                        .frame(width: 200, height: 21)
            )
                
            HStack {
                Image("magnifyingglass").resizable().aspectRatio(contentMode: .fill)
                    .frame(width:12, height: 12)
                    .padding(.leading, 5)
                    .opacity(0.8)
                TextField(placeholder, text: $query, onEditingChanged: { (editingChanged) in
                    if editingChanged {
                        self.isFocused = true
                    } else {
                        self.isFocused = false
                    }
                })
                    .textFieldStyle(PlainTextFieldStyle())
                if query != "" {
                    Button(action: {
                            self.query = ""
                    }) {
                        Image("xmark.circle.fill")
				            .resizable()
				            .aspectRatio(contentMode: .fit)
				            .frame(width:14, height: 14)
				            .padding(.trailing, 3)
				            .opacity(0.5)
                    }
				    .buttonStyle(PlainButtonStyle())
				    .opacity(self.query == "" ? 0 : 0.5)
                }
            }
            
        }
    }
}
```

Here's how you use it:

```swift
SearchTextField(query: $searchQuery, placeholder: "Search...")
  .frame(minWidth: 60.0, idealWidth: 200.0, maxWidth: 200.0, minHeight: 24.0, idealHeight: 21.0, maxHeight: 21.0, alignment: .center)
```

## Caveats
There are a few issues with this solution. Hopefully in a future version of SwiftUI this will be fixed.

### Removing the Focus Ring from All TextFields
This code creates a pretend TextField which holds the magnifying glass, a real TextField, and the close icon inside of it. If you remove the NSTextField extension you'll see the real textfield when focused. It's currently not possible to disable a specific focus ring so you'll have to reimplement them if you need to show them.

### Fake Focus Ring Doesn't Update on Focus
You'll notice in the GIF above that the focus ring does not show until you start typing. Not ideal but usable. If anyone has a suggestion for fixing this I'd be happy to know!

### No SF Symbols in MacOS
Surprisingly, MacOS does not have native support for SF Symbols! I used [this tool](https://github.com/knezzy/sfsymbols) to convert them to PNG and import them into my assets catalog. The symbols used are `xmark.circle.fill` and `magnifyingglass`.