在键入NSTextField时过滤NSTable - 自动选择第一行

Jer*_*jko 8 macos cocoa nstextfield nstableview swift

我有一个NSTextView字段过滤NSTable表作为输入中的用户类型.我已经成功实现了表过滤.

现在,我的目标是自动选择第一个结果(表中的第一行),并允许用户在键入搜索查询时使用箭头键在结果之间移动.在表格中的结果之间移动时,输入字段应保持聚焦.(这与Spotlight的工作方式类似).

这就是应用程序现在的样子:

应用

这是我的ViewController:

import Cocoa

class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate, NSTextFieldDelegate {

    @IBOutlet weak var field: NSTextField!
    @IBOutlet weak var table: NSTableView!

    var projects: [Project] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        projects = Project.all()

        field.delegate = self
        table.dataSource = self
        table.delegate = self
    }

    override func controlTextDidChange(_ obj: Notification) {
        let query = (obj.object as! NSTextField).stringValue

        projects = Project.all().filter { $0.title.contains(query) }

        table.reloadData()
    }

    func numberOfRows(in tableView: NSTableView) -> Int {
        return projects.count
    }

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "FirstCell"), owner: nil) as? NSTableCellView {
            cell.textField?.stringValue = projects[row].title
            return cell
        }

        return nil
    }
}
Run Code Online (Sandbox Code Playgroud)

这是Project上课

struct Project {
    var title: String = ""

    static func all() -> [Project] {
        return [
            Project(title: "first project"),
            Project(title: "second project"),
            Project(title: "third project"),
            Project(title: "fourth project"),
        ];
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢

Cha*_*tka 6

这有点,在@Willeke 发布的副本中已经有一个答案,但是 1) 该答案在 Objective-C 中,而不是 Swift,2) 我可以提供更详细的答案(带图片!),以及 3)我'我厚颜无耻地追求赏金(收购规则#110)。因此,考虑到这一点,以下是我将如何实施您要执行的操作:

不要使用NSTextView; 使用NSTextField,甚至更好的NSSearchField. NSSearchField很棒,因为我们可以在 Interface Builder 中设置它,几乎不需要代码就可以创建过滤谓词。我们要做的就是NSPredicate在我们的视图控制器中创建一个属性,然后设置搜索字段的 Bindings Inspector 以指向它:

在此处输入图片说明

然后你可以创建一个数组控制器,它的过滤谓词绑定到相同的属性,它的Content Array绑定绑定到视图控制器上的一个属性:

在此处输入图片说明

而且,当然,将表视图绑定到数组控制器:

在此处输入图片说明

最后但并非最不重要的一点是,将表格单元格视图中的文本字段绑定到title属性:

在此处输入图片说明

有了 Interface Builder 中的所有设置,我们几乎不需要任何代码。我们需要的只是Project类的定义(所有属性都需要标记,@objc以便 Cocoa Bindings 系统可以看到它们):

class Project: NSObject {
    @objc let title: String

    init(title: String) {
        self.title = title
        super.init()
    }
}
Run Code Online (Sandbox Code Playgroud)

我们还需要视图控制器上的项目属性、数组控制器和过滤谓词。过滤谓词需要dynamic这样才能在 Cocoa Bindings 更改和更新 UI 时得到通知。如果projects可以更改,也进行更改,dynamic以便对其进行的任何更改都将反映在 UI 中(否则,您可以摆脱dynamic并仅进行更改@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
}
Run Code Online (Sandbox Code Playgroud)

而且,最后但并非最不重要的是,我们的视图控制器上的扩展符合它NSSearchFieldDelegate(或者NSTextFieldDelegate如果您使用的是NSTextField代替NSSearchField),我们将在其上实现该control(:textView:doCommandBy:)方法。基本上,我们拦截搜索字段的字段编辑器正在执行的文本编辑命令,如果我们得到moveUp:moveDown:,则返回true以告诉字段编辑器我们将处理这些命令。对于除这两个选择器之外的所有内容,返回false告诉字段编辑器执行它通常会执行的操作。

请注意,这就是您应该使用NSTextFieldorNSSearchField而不是NSTextView;的原因。这个委托方法只会NSControl被子类调用,而NSTextView不会。

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

瞧!

(当然,如果您更喜欢手动填充表格视图而不是使用绑定,您可以忽略大部分内容,只需实现control(:textView:doCommandBy:),手动更新您的表格选择,而不是要求您的数组控制器执行此操作。当然,使用绑定会导致结果用漂亮、干净的代码,这就是我更喜欢它的原因。)