Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Remove 'Focus Ring' Highlight Border from macOS TextField

I used the below code to create a custom search bar in SwiftUI. It works great on iOS / Catalyst:

SearchTextView on iOS / Catalyst

...but when running natively on macOS, the 'focus ring' highlighted border styling (when the user selects the text field) rather ruins the effect:

SearchTextView on Native macOS

Using .textFieldStyle(PlainTextFieldStyle()) has removed most of the default styling from the underlying field (which I believe is an NSTextField), but not the focus ring.

Is there a way to remove this too? I tried creating a custom TextFieldStyle and applying that, but couldn't find any modifier to style that border.

public struct SearchTextView: View {
    
    @Binding var searchText: String

    #if !os(macOS)
    private let backgroundColor = Color(UIColor.secondarySystemBackground)
    #else
    private let backgroundColor = Color(NSColor.controlBackgroundColor)
    #endif
    
    public var body: some View {
        
        HStack {
            
            Spacer()
            
            #if !os(macOS)
            Image(systemName: "magnifyingglass")
            #else
            Image("icons.general.magnifyingGlass")
            #endif
            
            TextField("Search", text: self.$searchText)
                .textFieldStyle(PlainTextFieldStyle())
                .foregroundColor(.primary)
                .padding(8)
        
            Spacer()
        }
        .foregroundColor(.secondary)
        .background(backgroundColor)
        .cornerRadius(12)
        .padding()
    }
    
    public init(searchText: Binding<String>) {
        self._searchText = searchText
    }
}
like image 724
TheNeil Avatar asked Jan 19 '20 20:01

TheNeil


1 Answers

As stated in an answer by Asperi to a similar question here, it's not (yet) possible to turn off the focus ring for a specific field using SwiftUI; however, the following workaround will disable the focus ring for all NSTextField instances in the app:

extension NSTextField {
    open override var focusRingType: NSFocusRingType {
        get { .none }
        set { }
    }
}

If you want to replace this with your own custom focus ring within the view, the onEditingChanged parameter can help you achieve this (see below example); however, it's unfortunately called on macOS when the user types the first letter, not when they first click on the field (which isn't ideal).

In theory, you could use the onFocusChange closure in the focusable modifier instead, but that doesn't appear to get called for these macOS text fields currently (as of macOS 10.15.3).

public struct SearchTextView: View {

    @Binding var searchText: String

    @State private var hasFocus = false

    #if !os(macOS)
    private var backgroundColor = Color(UIColor.secondarySystemBackground)
    #else
    private var backgroundColor = Color(NSColor.controlBackgroundColor)
    #endif

    public var body: some View {

        HStack {

            Spacer()

            #if !os(macOS)
            Image(systemName: "magnifyingglass")
            #else
            Image("icons.general.magnifyingGlass")
            #endif

            TextField("Search", text: self.$searchText, onEditingChanged: { currentlyEditing in
                self.hasFocus = currentlyEditing // If the editing state has changed to be currently edited, update the view's state
            })
                .textFieldStyle(PlainTextFieldStyle())
                .foregroundColor(.primary)
                .padding(8)

            Spacer()
        }
        .foregroundColor(.secondary)
        .background(backgroundColor)
        .cornerRadius(12)
        .border(self.hasFocus ? Color.accentColor : Color.clear, width: self.hasFocus ? 3 : 0)
        .padding()
    }

    public init(searchText: Binding<String>) {
        self._searchText = searchText
    }
}
like image 181
TheNeil Avatar answered Jan 01 '23 09:01

TheNeil