#if os(macOS) import AppKit import SwiftUI /// Position the field editor's caret as part of gaining focus, before another /// keyboard event can replace the character that started cell editing. struct SheetCellEditor: NSViewRepresentable { @Binding var text: String let enabled: Bool let label: String let commit: (Bool) -> Void let cancel: () -> Void func makeCoordinator() -> Coordinator { Coordinator(self) } func makeNSView(context: Context) -> CellTextField { let field = CellTextField() field.isBordered = false; field.drawsBackground = false // The cell already draws its selection border around the full cell. field.focusRingType = .none field.font = .systemFont(ofSize: 13) field.delegate = context.coordinator field.stringValue = text field.setAccessibilityLabel(label) return field } func updateNSView(_ field: CellTextField, context: Context) { context.coordinator.parent = self let regainedEditing = enabled && !field.isEnabled field.isEnabled = enabled if field.stringValue != text { field.stringValue = text if let editor = field.currentEditor() as? NSTextView { editor.setSelectedRange(NSRange(location: text.utf16.count, length: 0)) } } if regainedEditing { field.window?.makeFirstResponder(field) } } final class CellTextField: NSTextField { override func viewDidMoveToWindow() { super.viewDidMoveToWindow() guard let window else { return } // The representable must be attached before it can become responder. DispatchQueue.main.async { [weak self, weak window] in guard let self, let window, self.window === window, self.isEnabled else { return } window.makeFirstResponder(self) } } override func becomeFirstResponder() -> Bool { let accepted = super.becomeFirstResponder() if accepted, let editor = currentEditor() as? NSTextView { editor.setSelectedRange(NSRange(location: editor.string.utf16.count, length: 0)) } return accepted } } @MainActor final class Coordinator: NSObject, NSTextFieldDelegate { var parent: SheetCellEditor init(_ parent: SheetCellEditor) { self.parent = parent } func controlTextDidChange(_ notification: Notification) { guard let field = notification.object as? NSTextField else { return } parent.text = field.stringValue } func control(_ control: NSControl, textView: NSTextView, doCommandBy selector: Selector) -> Bool { switch selector { case #selector(NSResponder.insertNewline(_:)): parent.commit(false) case #selector(NSResponder.insertTab(_:)): parent.commit(true) case #selector(NSResponder.cancelOperation(_:)): parent.cancel() default: return false } return true } } } #endif