NSCollectionView类型选择

问题描述 投票:0回答:1

我已经为我的 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 添加/删除项目。

swift nscollectionview nsevent
1个回答
0
投票

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)
}

谢谢。我还需要做一些清理工作,但我认为这就是答案。

© www.soinside.com 2019 - 2024. All rights reserved.