【问题标题】:Filtering NSTable while typing into NSTextField - auto-select first row在输入 NSTextField 时过滤 NSTable - 自动选择第一行
【发布时间】:2018-01-27 22:00:57
【问题描述】:

我有一个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
    }
}

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

谢谢

【问题讨论】:

  • 问题是什么?如何选择一行,如何检测方向键,如何将方向键发送到表格视图或其他?
  • @Willeke 问题是如何实现“聚焦”TextField 和 Table。就像聚光灯一样。它允许您在搜索字段中输入字符,同时使用箭头从结果中进行选择。

标签: swift macos cocoa nstableview nstextfield


【解决方案1】:

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

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

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

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

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

在 Interface Builder 中进行所有设置后,我们几乎不需要任何代码。我们只需要定义Project 类(所有属性都需要标记@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:,则返回true 告诉字段编辑器我们将处理这些命令。对于这两个选择器以外的所有内容,返回 false 以告诉字段编辑器执行它通常会执行的操作。

请注意,这就是您应该使用NSTextFieldNSSearchField 而不是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
        }
    }
}

瞧!

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

【讨论】:

    【解决方案2】:

    正如@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(_:) 选择器(通过按向上箭头触发)之前首先检查委托。在这里,函数响应说“不要做你通常做的事情,委托会处理它”(通过返回true)并将事件发送到NSTableView 对象。焦点不会丢失在文本字段上。

    【讨论】:

    • 这个答案需要提到它只适用于NSControl 子类,例如NSTextFieldNSSearchField。对于 OP 当前正在使用的 NSTextView,将不会调用此委托方法,并且此技术将不起作用。
    猜你喜欢
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 2013-05-20
    • 1970-01-01
    • 2019-01-13
    • 2023-03-22
    • 1970-01-01
    相关资源
    最近更新 更多