我已经为我的 NSCollectionView 实现实现了类型选择。
在我的集合视图子类中,我添加了额外的委托方法:
protocol CollectionViewDelegate : NSCollectionViewDelegate {
func collectionViewDelegateTypeSelect(_ text: String)
func collectionViewDelegateResetTypeSelect()
}
class CollectionView : NSCollectionView {
override func keyDown(with event: NSEvent) {
if let del = delegate as? CollectionViewDelegate {
if (event.characters?.rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil) || (event.characters == " ") {
del.collectionViewDelegateTypeSelect(event.characters!)
return
}
else {
del.collectionViewDelegateResetTypeSelect()
}
}
if (event.specialKey == NSEvent.SpecialKey.pageUp) {
scroll(NSMakePoint(0, max(0, enclosingScrollView!.documentVisibleRect.minY - enclosingScrollView!.documentVisibleRect.height)))
} else if (event.specialKey == NSEvent.SpecialKey.pageDown) {
scroll(NSMakePoint(0, min(enclosingScrollView!.documentVisibleRect.maxY, enclosingScrollView!.documentVisibleRect.origin.y + enclosingScrollView!.documentVisibleRect.height)))
} else if (event.specialKey == NSEvent.SpecialKey.home) {
scroll(NSMakePoint(0, 0))
} else if (event.specialKey == NSEvent.SpecialKey.end) {
scroll(NSMakePoint(0, frame.height))
} else {
super.keyDown(with: event)
}
}
}
然后在我的委托(NSArrayController 的子类)中,我处理类型选择:
var timer: Timer?
var typeString: String = ""
var typeCount: Int = 0
func collectionViewDelegateTypeSelect(_ text: String) {
typeCount = 0
typeString.append(text)
findItemClosestToString(typeString)
guard let _ = timer, timer!.isValid else {
timer = Timer(timeInterval: 0.1, repeats: true, block: {_ in
self.typeCount = self.typeCount + 1
if self.typeCount > 10 {
self.typeCount = 0
self.typeString = ""
self.timer?.invalidate()
}
})
RunLoop.main.add(timer!, forMode: RunLoop.Mode.default)
return
}
}
func collectionViewDelegateResetTypeSelect() {
if collectionViewDelegateIsTypeSelecting() {
timer?.invalidate()
timer = nil
}
}
func sortedItems() -> ([NSManagedObject], [String]) {
guard let items = (arrangedObjects as? [MediaItem]) else { return ([], []) }
let sorted = (items as NSArray).sortedArray(using: [alphabeticalSortDescriptor]) as! [MediaSeries]
return (sorted, sorted.map { $0.title })
}
func findItemClosestToString(_ text: String) {
let (items, strings) = sortedItems()
guard items.count > 0 else { return }
var lo = 0
var hi = items.count - 1
var mid = lo
while lo <= hi {
mid = (lo + hi)/2
let c = text.caseInsensitiveCompare(strings[mid])
if c == .orderedDescending {
lo = mid + 1
} else if c == .orderedAscending {
hi = mid - 1
} else {
break
}
}
if strings[mid].localizedLowercase.hasPrefix(text.localizedLowercase) == false, mid + 1 < strings.count, strings[mid + 1].localizedLowercase.hasPrefix(text.localizedLowercase) {
mid = mid + 1
}
selectItem(items, mid: mid)
}
func selectItem(_ items: [NSManagedObject], mid: Int) {
let index = (arrangedObjects as! [NSManagedObject]).firstIndex(of: items[mid])!
let path = IndexPath(item: index, section: 0)
mainCollection.deselectAll(nil)
//mainCollection.reloadData() - This was left over debug
mainCollection.selectItems(at: [path], scrollPosition: .top)
collectionView(mainCollection, didSelectItemsAt: [path])
}
这工作得很好,但是,随着集合视图显示的项目数量增加(> 1000),输入速度可能比类型选择速度更快,让用户只能观看 UI 几秒钟的追赶。
选择功能被分解,以便在子类中更容易覆盖各个部分。
我的问题是如何最好地整理多个 NSEvents,以便在少数事件排队的情况下,仅添加角色,从而发生不必要的中间选择。我能想到的唯一方法是添加一个额外的计时器,在运行主要选择算法之前收集一个或多个事件。因此,在用户输入之间发生的所有事情都是
typeString
的长度增长,而不进一步进行选择,直到我有理由确定不会有额外的输入进入。
当我开始解决这个问题时,我认为必须有更好的方法。我知道 NSTableViews 和 NSOutlineViews 支持开箱即用的类型选择,但 NSCollectionViews 似乎不支持。
我很好奇的是是否有更好的方法我完全错过了。我还没有发现其他人尝试向 NSCollectionView 添加类型选择。
编辑:澄清一下,此代码可以工作,但在集合视图中包含大量项目时速度很慢。我可以通过更改
collectionViewDelegateTypeSelect()
的工作方式来使其更快,以等待计时器的几次迭代来收集其他字符,因此总体上完成的选择事件较少。然而,这看起来有点令人讨厌,让我觉得一定有更好的方法我错过了。
委托是绑定到 CollectionView 的 NSArrayController。它是可变的,并且可以使用多个不同的用户定义的 NSSortDescriptor 进行排序。它是一个可变数组,用户可以向 CollectionView 添加/删除项目。
Willeke 使用 DispatchQueue 的想法效果很好。我需要做一些微调来找出延迟未来执行的最佳时间。
var clearTypeSelect: DispatchWorkItem?
var issueTypeSelect: DispatchWorkItem?
var typeString: String = ""
var typeSelect: Bool = false
override func collectionViewDelegateTypeSelect(_ text: String) {
clearTypeSelect?.cancel()
issueTypeSelect?.cancel()
typeString.append(text)
let clearWorkItem = DispatchWorkItem {
self.typeString = ""
}
clearTypeSelect = clearWorkItem
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(1000), execute: clearWorkItem)
let selectWorkItem = DispatchWorkItem {
DispatchQueue.main.async {
self.findItemClosestToString(self.typeString)
}
}
issueTypeSelect = selectWorkItem
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(300), execute: selectWorkItem)
}
谢谢。我还需要做一些清理工作,但我认为这就是答案。