在输入 NSTextField 时过滤 NSTable - auto-select 第一行

Filtering NSTable while typing into NSTextField - auto-select first row

我有一个 NSTextView 字段,它在用户输入时过滤 NSTable table。我已成功实施 table 过滤。

现在,我的目标是自动 select 第一个结果(table 中的第一行)并允许用户在键入搜索查询时使用箭头键在结果之间移动.在 table 中的结果之间移动时,输入字段应保持焦点。 (这类似于 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 { [=11=].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
    }
}

这是Projectclass

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"),
        ];
    }
}

谢谢

正如@Willeke 指出的那样,这很可能是重复的。另一个问题的解决方案在这里有效。我已将其转换为 swift 并添加了一些解释。

我用 NSSearchField 而不是 NSTextField 对此进行了测试,但我希望它能正常工作。

首先,您需要将 NSControlTextEditingDelegate 协议添加到您的 ViewController,并添加以下函数:

func control(_ control: NSControl, textView: NSTextView,
             doCommandBy commandSelector: Selector) -> Bool {
    if commandSelector == #selector(moveUp(_:)) {
        table.keyDown(with: NSApp.currentEvent!)
        return true
    } else if commandSelector == #selector(moveDown(_:)) {
        table.keyDown(with: NSApp.currentEvent!)
        return true
    }
    return false
}

您已经将文本字段的委托设置为 ViewController,所以您已经设置好了。

这将导致您的 NSTextField 在执行 moveUp(_:) 选择器(通过按向上箭头触发)之前首先检查委托。在这里,函数响应说 "don't do what you normally do, the delegate will handle it"(通过返回 true)并将事件发送到 NSTableView 对象。焦点不会丢失在文本字段上。

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

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

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

当然,还要将 table 视图绑定到数组控制器:

最后但同样重要的是,将 table 的单元格视图中的文本字段绑定到 title 属性:

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

class Project: NSObject {
    @objc let title: String

    init(title: String) {
        self.title = title
        super.init()
    }
}

我们还需要在视图控制器上为项目、数组控制器和过滤谓词设置属性。过滤器谓词需要 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
}

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

请注意,这就是您应该使用 NSTextFieldNSSearchField 而不是 NSTextView 的原因;此委托方法只会为 NSControl subclasses 调用,而 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
        }
    }
}

瞧!

(当然,如果您更喜欢手动填充 table 视图而不是使用绑定,您可以忽略其中的大部分内容,只需实施 control(:textView:doCommandBy:),更新您的 table' s 的选择是手动的,而不是让你的数组控制器来做。当然,使用绑定会产生漂亮、干净的代码,这就是我喜欢它的原因。)