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
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
}