在输入 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
}
}
这是Project
class
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
,甚至更好,使用 NSSearchField
。 NSSearchField
很棒,因为我们可以在 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
告诉字段编辑器执行它通常执行的操作。
请注意,这就是您应该使用 NSTextField
或 NSSearchField
而不是 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 的选择是手动的,而不是让你的数组控制器来做。当然,使用绑定会产生漂亮、干净的代码,这就是我喜欢它的原因。)
我有一个 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
}
}
这是Project
class
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
,甚至更好,使用 NSSearchField
。 NSSearchField
很棒,因为我们可以在 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
告诉字段编辑器执行它通常执行的操作。
请注意,这就是您应该使用 NSTextField
或 NSSearchField
而不是 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 的选择是手动的,而不是让你的数组控制器来做。当然,使用绑定会产生漂亮、干净的代码,这就是我喜欢它的原因。)