Issue #589

Use onTapGesture

import SwiftUI

struct MyTextField: View {
    @Binding
    var text: String
    let placeholder: String
    @State
    private var isFocus: Bool = false

    var body: some View {
        FocusTextField(text: $text, placeholder: placeholder, isFocus: $isFocus)
            .padding()
            .cornerRadius(4)
            .overlay(
                RoundedRectangle(cornerRadius: 4)
                    .stroke(isFocus ? Color.accentColor: Color.separator)
            )
            .onTapGesture {
                isFocus = true
            }
    }
}

private struct FocusTextField: NSViewRepresentable {
    @Binding
    var text: String
    let placeholder: String
    @Binding
    var isFocus: Bool

    func makeNSView(context: Context) -> NSTextField {
        let tf = NSTextField()
        tf.focusRingType = .none
        tf.isBordered = false
        tf.isEditable = true
        tf.isSelectable = true
        tf.drawsBackground = false
        tf.delegate = context.coordinator
        tf.placeholderString = placeholder
        return tf
    }

    func updateNSView(
        _ nsView: NSTextField,
        context: Context
    ) {
        nsView.font = NSFont.preferredFont(forTextStyle: .body, options: [:])
        nsView.textColor = NSColor.labelColor
        nsView.stringValue = text
    }

    func makeCoordinator() -> FocusTextField.Coordinator {
        Coordinator(parent: self)
    }

    class Coordinator: NSObject, NSTextFieldDelegate  {
        let parent: FocusTextField
        init(parent: FocusTextField) {
            self.parent = parent
        }

        func controlTextDidBeginEditing(_ obj: Notification) {
            parent.isFocus = true
        }

        func controlTextDidEndEditing(_ obj: Notification) {
            parent.isFocus = false
        }

        func controlTextDidChange(_ obj: Notification) {
            let textField = obj.object as! NSTextField
            parent.text = textField.stringValue
        }
    }
}

becomeFirstResponder

class FocusAwareTextField: NSTextField {
    var onFocusChange: (Bool) -> Void = { _ in }

    override func becomeFirstResponder() -> Bool {
        let textView = window?.fieldEditor(true, for: nil) as? NSTextView
        textView?.insertionPointColor = R.nsColor.action
        onFocusChange(true)
        return super.becomeFirstResponder()
    }
}

textField.delegate // NSTextFieldDelegate
func controlTextDidEndEditing(_ obj: Notification) {
    onFocusChange(false)
}

NSTextField and NSText

https://stackoverflow.com/questions/25692122/how-to-detect-when-nstextfield-has-the-focus-or-is-its-content-selected-cocoa

When you clicked on search field, search field become first responder once, but NSText will be prepared sometime somewhere later, and the focus will be moved to the NSText.

I found out that when NSText is prepared, it is set to self.currentEditor() . The problem is that when becomeFirstResponder()’s call, self.currentEditor() hasn’t set yet. So becomeFirstResponder() is not the method to detect it’s focus.

On the other hand, when focus is moved to NSText, text field’s resignFirstResponder() is called, and you know what? self.currentEditor() has set. So, this is the moment to tell it’s delegate that that text field got focused

Use NSTextView

Any time you want to customize NSTextField, use NSTextView instead

// NSTextViewDelegate
func textDidBeginEditing(_ notification: Notification) {
    parent.isFocus = true
}

func textDidEndEditing(_ notification: Notification) {
    parent.isFocus = false
}