This view, sorta has the answer already in the duplicate sent by @Willeke, but 1) that the answer is in Objective-C, and not in Swift, 2). I can give a slightly more detailed answer (with pictures!) And 3) I shamelessly go after generosity (Acquisition Rule No. 110). So, keeping in mind, this is how I implement what you are trying to do:
Do not use NSTextView ; use an NSTextField , or even better, an NSSearchField . NSSearchField excellent because we can configure it in Interface Builder to create a filter predicate with almost no code. All we need to do is create an NSPredicate property in our view controller, and then configure the Bindings Inspector search field to point to it:

You can then create an Array Controller with its Predicate filter bound to the same property and a Content Array binding bound to the property on the view controller:

And, of course, bind the table view to the array controller:

And last but not least, attach the text box in the table cell view to the title property:

When using everything that is configured in Interface Builder, we hardly need any code. All we need is a definition of the Project class (all properties must be marked with @objc so that Cocoa Bindings can see them):
class Project: NSObject { @objc let title: String init(title: String) { self.title = title super.init() } }
We also need properties on our project view controller, array controller, and filter predicate. The filter predicate must be dynamic , so Cocoa Bindings can be notified when the user interface changes and updates. If projects can change, make it dynamic too, so that any changes in it are reflected in the user interface (otherwise you can get rid of dynamic and just make it @objc let ).
class ViewController: NSViewController { @IBOutlet var arrayController: NSArrayController! @objc dynamic var projects = [ Project(title: "Foo"), Project(title: "Bar"), Project(title: "Baz"), Project(title: "Qux") ] @objc dynamic var filterPredicate: NSPredicate? = nil }
And last but not least, an extension of our view controller, corresponding to its NSSearchFieldDelegate (or NSTextFieldDelegate , if you use NSTextField instead of NSSearchField ), on which we will implement the control(:textView:doCommandBy:) . Basically, we intercept text editing commands executed by the search field field editor, and if we get moveUp: or moveDown: return true to tell the field editor that we will process these commands. For all but these two selectors, return false to tell the field editor to do what it usually does.
Note that this is the reason you should use NSTextField or NSSearchField rather than NSTextView ; this delegate method will only be called for subclasses of NSControl , which NSTextView not.
extension ViewController: NSSearchFieldDelegate { func control(_: NSControl, textView _: NSTextView, doCommandBy selector: Selector) -> Bool { switch selector { case #selector(NSResponder.moveUp(_:)): self.arrayController.selectPrevious(self) return true case #selector(NSResponder.moveDown(_:)): self.arrayController.selectNext(self) return true default: return false } } }
Voila!
(Of course, if you prefer to fill out the table view manually instead of using bindings, you can ignore most of this and simply implement control(:textView:doCommandBy:) by updating the table selection manually, instead of asking your array controller to do this. Bindings, of course lead to nice, clean code, so I prefer it.)