NSScrollView封装NSCollectionView在调整大小时总是向后滚动

NSScrollView encapsulating NSCollectionView always scrolling back when resizing

程序打开一个 window,其中包含一个包含 1000 个项目的集合视图。如果我向下滚动一点,然后向任何方向调整 window 的大小,滚动视图将立即跳回顶部。

应用程序是在没有 Storyboard 或 XIB 的情况下构建的。 通过 Interface Builder 构建类似的应用程序时不会出现此问题。 似乎缺少某些东西,Interface Builder 默认配置的东西。我正在 macOS 11.2.3 上使用 Xcode 12.4 进行测试。有什么想法吗?

为了便于重现,我将所有内容打包到一个文件中 main.swift.:

import Cocoa

let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

class AppDelegate: NSObject, NSApplicationDelegate, NSSplitViewDelegate {
    var window: NSWindow?
    var dataSource: CollectionViewDataSource?
    var collectionView: NSCollectionView?
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let screenSize = NSScreen.main?.frame.size ?? NSSize(width: 1920, height: 1080)
        window = NSWindow(contentRect: NSMakeRect(screenSize.width/4, screenSize.height/4, screenSize.width/2, screenSize.height/2),
                          styleMask: [.miniaturizable, .closable, .resizable, .titled],
                          backing: .buffered,
                          defer: false)
        window?.makeKeyAndOrderFront(nil)
        
        if let view = window?.contentView {
            collectionView = NSCollectionView(frame: NSZeroRect)
            dataSource = CollectionViewDataSource()
            let scrollView = NSScrollView(frame: NSZeroRect)
            view.addSubview(scrollView)
            scrollView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                scrollView.topAnchor.constraint(equalTo: view.topAnchor),
                scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            ])
            scrollView.documentView = collectionView
            
            collectionView?.collectionViewLayout = NSCollectionViewFlowLayout()
            collectionView?.register(CollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"))
            collectionView?.dataSource = dataSource
        }
    }
}

class CollectionViewDataSource: NSObject, NSCollectionViewDataSource {
    func numberOfSections(in collectionView: NSCollectionView) -> Int {
        return 1
    }
    
    func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
        return 1000
    }
    
    func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
        let i = collectionView.makeItem(withIdentifier:NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"), for:indexPath) as! CollectionViewItem
        i.view.wantsLayer = true
        i.view.layer?.backgroundColor = NSColor.init(colorSpace: NSColorSpace.deviceRGB, hue: CGFloat(Float.random(in: 0..<1)), saturation: CGFloat(Float.random(in: 0.4...1)), brightness: CGFloat(Float.random(in: 0.5...1)), alpha: 1).cgColor
        return i
    }
    
    func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
        fatalError("Not implemented")
    }
}

class CollectionViewItem : NSCollectionViewItem {
    override func loadView() {
        self.view = NSView(frame: NSZeroRect)
    }
}

故事板中集合视图的源代码:

<collectionView id="yh4-in-fLt">
    <rect key="frame" x="0.0" y="0.0" width="480" height="158"/>
    <autoresizingMask key="autoresizingMask" widthSizable="YES"/>
    <collectionViewFlowLayout key="collectionViewLayout" minimumInteritemSpacing="10" minimumLineSpacing="10" id="TGK-mX-O4r">
        <size key="itemSize" width="50" height="50"/>
    </collectionViewFlowLayout>
    <color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</collectionView>

但是 autoresizingMask 在 IB 中不可见。设置 autoresizingMask 解决了这个问题。

collectionView.autoresizingMask = [.width]

忽略帧更改通知似乎可以解决问题。

添加自定义 NSClipView 作为 NSScrollView 的内容视图,如下所示:

scrollView.contentView = ClipViewIgnoringFrameChange()

其中 ClipViewIgnoringFrameChange() 定义如下:

class ClipViewIgnoringFrameChange : NSClipView {
    override func viewFrameChanged(_ notification: Notification) {}
}