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

时间:2018-01-27 22:00:58

标签: swift macos cocoa nstableview nstextfield

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

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

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

app

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

谢谢

2 个答案:

答案 0 :(得分:4)

这种,sorta在@Willeke发布的副本中已有答案,但1)答案在Objective-C中,而不是Swift,2)我可以提供更详细的答案(带图片!),以及3)我肆无忌惮地追求赏金(收购规则#110)。所以,考虑到这一点,这就是我如何实现你想要做的事情:

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

enter image description here

然后你可以创建一个Array Controller,它的Filter Predicate绑定到同一个属性,并且它的Content Array绑定绑定到视图控制器上的属性:

enter image description here

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

enter image description here

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

enter image description here

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

class Project: NSObject {
    @objc let title: String

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

我们还需要在视图控制器上为项目,数组控制器和过滤谓词提供属性。过滤谓词需要为dynamic,以便可变绑定在更改和更新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不是。{/ p>

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:),手动更新表的选择,而不是让你的阵列控制器去做当然,使用绑定会产生漂亮,干净的代码,这就是我喜欢它的原因。)

答案 1 :(得分:1)

@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对象。焦点不会在文本字段中丢失。