我怎样才能滚动到 SwiftUI 中托管的 NSCollectionView?

问题描述

我使用 NSCollectionView 构建了一个 NSViewRepresentable 包装器,但它拒绝滚动。这个类看起来像这样:

final class SwiftNSCollectionView: NSObject,NSViewRepresentable,NSCollectionViewDataSource // etc
{
// ... init ...

    typealias NSViewType = NSCollectionView
    
    func makeNSView(context: Context) -> NSCollectionView {
        let collectionView = NSCollectionView()
        scrollView.documentView = collectionView
        
        updateNSView(collectionView,context: context)
        
        return collectionView
    }
    
    func updateNSView(_ scrollView: NSCollectionView,context: Context) {
        collectionView.dataSource = self
        // ... other collectionView setup
    }

    // ...
}

通常,NSCollectionView一个内置的 NSScrollView。

我试过了:

  • 没有包装器——NSCollectionView 根本不滚动。

  • 将此 SwiftNSCollectionView 包装在 SwiftUI ScrollView 中,但这会导致两个问题:

    • NSCollectionView 的高度折叠为 0(我可以使用 GeometryReader 解决这个问题)
    • NSCollectionView 不想扩展到其所有对象的高度(这是有道理的,因为它将它们虚拟化)
  • 使用带有正交滚动方向的 NSCollectionViewCompositionalLayoutConfiguration (hacky):

    let section = NSCollectionLayoutSection(group: group)
    section.orthogonalScrollingBehavior = .continuous
    
    // If the "official" scroll direction is horizontal,// then the orthogonal direction becomes vertical,// and we can scroll our one section ?
    let configuration = NSCollectionViewCompositionalLayoutConfiguration()
    configuration.scrollDirection = .horizontal
    

    但这似乎打乱了键盘输入:在一定数量的元素之后,元素之间的箭头键要么完全停止工作,要么移动到完全错误的元素上。看起来像是某种虚拟化错误

解决方法

您可以通过手动创建 NSScrollView 来缓解这种情况。

  1. NSViewType 更新为 NSScrollView
  2. 根据需要更新函数签名。
  3. 使用您现有的 NSCollectionView 作为新滚动视图的 .documentView

然后您可以直接在 SwiftUI 代码中使用您的 SwiftNSCollectionView,它会正确滚动,而无需您进行任何自定义工作。

final class SwiftNSCollectionView: NSObject,NSViewRepresentable,NSCollectionViewDataSource // etc {
    // ... init ...

    // No longer NSCollectionView
    typealias NSViewType = NSScrollView
    
    func makeNSView(context: Context) -> NSScrollView {
        // Create an NSScrollView,too!
        let scrollView = NSScrollView()
        let collectionView = NSCollectionView()
        scrollView.documentView = collectionView
        
        updateNSView(scrollView,context: context)
        
        return scrollView
    }
    
    func updateNSView(_ scrollView: NSScrollView,context: Context) {
        // Since we get an NSScrollView,get the child!
        let collectionView = scrollView.documentView as! NSCollectionView
        collectionView.dataSource = self
        // ... other collectionView setup
    }

    // ...
}