NSScrollView, инкапсулирующий NSCollectionView, всегда прокручивается назад при изменении размера

Программа открывает окно, содержащее представление коллекции с 1000 элементами. Если я прокрутлю немного вниз, а затем изменю размер окна на любую величину в любом направлении, вид прокрутки немедленно вернется к началу.

Приложение построено без Storyboard или XIB. Проблема не возникает при создании аналогичного приложения с помощью Interface Builder. Кажется, чего-то не хватает, что-то, что Interface Builder настраивает по умолчанию. Я тестирую это с Xcode 12.4 на macOS 11.2.3. Любые идеи?

Для простоты воспроизведения я запаковал все в файл 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)
    }
}


person C. Ocoa    schedule 06.04.2021    source источник


Ответы (2)


Исходный код представления коллекции в раскадровке:

<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]
person Willeke    schedule 07.04.2021
comment
Спасибо за ваш ответ. Однако это не работает для кода, опубликованного в вопросе. Мне удалось заставить его работать, заставив просмотр клипа игнорировать изменение кадра. - person C. Ocoa; 15.04.2021

Игнорирование уведомления об изменении кадра, похоже, помогает.

Добавьте пользовательское представление NSClipView в качестве содержимого NSScrollView следующим образом:

scrollView.contentView = ClipViewIgnoringFrameChange()

где ClipViewIgnoringFrameChange() определяется следующим образом:

class ClipViewIgnoringFrameChange : NSClipView {
    override func viewFrameChanged(_ notification: Notification) {}
}
person C. Ocoa    schedule 15.04.2021