嵌入NSMenu的NSCollectionView在菜单未显示时无法正常更新
我希望将NSCollectionView嵌入NSMenu中,实现方式非常简单:
private func configurePlaylistLibraryMenu() { let playlistlibraryvievController = PlaylistLabraryViewController(player: player, extensionHandler: extensionHandler) let item = NSMenuItem() item.view = playlistlibraryvievController.view playlistLibraryMenu.addItem(item) } @IBAction func showPlaylists(_ sender: NSButton) { let position = NSPoint(x: sender.frame.origin.x, y: sender.frame.origin.y - 5) playlistLibraryMenu.popUp(positioning: playlistLibraryMenu.item(at: 0), at: position, in: view) }
对应的PlaylistLabraryViewController代码如下:
import Cocoa import Combine import SDWebImage class PlaylistLabraryViewController: NSViewController { struct ElementKind { static let overlay = "overlay-element-kind" } enum Section { case main } @IBOutlet weak var collectionView: NSCollectionView! private var dataSource: NSCollectionViewDiffableDataSource<Section, SourceInfo>! = nil private var publishers = Set<AnyCancellable>() private var playlistLibrary: [SourceInfo] = [] private var extensionHandler: SafariExtensionHandler? private var player: Player init(player: Player, extensionHandler: SafariExtensionHandler?) { self.player = player self.extensionHandler = extensionHandler super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() configurePlaylistCollectionView() configurePublishers() } private func configurePlaylistCollectionView() { collectionView.backgroundColors = [.clear] configureHierarchy() configureDataSource() } private func configurePublishers() { player.publisher(for: \.playlistDataArray).sink { [self] playlistDataArray in var newPlaylistLibrary: [SourceInfo] = [] playlistDataArray.forEach { do { let playlistInfo = try PropertyListDecoder().decode(SourceInfo.self, from: $0) newPlaylistLibrary.append(playlistInfo) } catch { NSApp.presentError(error) } } let difference = newPlaylistLibrary.difference(from: playlistLibrary, by: { $0.link == $1.link }) var currentSnapshot = dataSource.snapshot() var animate = true for change in difference { switch change { case .insert(_ , let playlist, _): playlistLibrary.append(playlist) currentSnapshot.appendItems([playlist]) animate = false case .remove(_ ,let playlist, _): playlistLibrary.remove(playlist) currentSnapshot.deleteItems([playlist]) animate = true } } dataSource.apply(currentSnapshot, animatingDifferences: animate) }.store(in: &publishers) } } // MARK: - Playlist Library actions extension PlaylistLabraryViewController { private func deletePlaylist(at indexPath: IndexPath) { let _ = player.playlistDataArray.remove(at: indexPath.item) player.savePlaylistDataArray() } private func pausePlayer() { extensionHandler?.performPlayerAction(.togglePause, actionProperties: [PlayerAction.togglePause.string: 1]) } private func playPlaylist(at indexPath: IndexPath) { let playlist = playlistLibrary[indexPath.item] extensionHandler?.performPlayerAction(.playPlaylist, actionProperties: [PlayerAction.playPlaylist.string: playlist.link]) } private func toggleLike() { log(#function) } } // MARK: - CollectionView methods extension PlaylistLabraryViewController { private func configureHierarchy() { let itemNib = NSNib(nibNamed: "PlaylistItem", bundle: nil) collectionView.register(itemNib, forItemWithIdentifier: PlaylistItem.reuseIdentifier) collectionView.collectionViewLayout = createLayout() } private func configureDataSource() { dataSource = NSCollectionViewDiffableDataSource<Section, SourceInfo>(collectionView: collectionView, itemProvider: { (collectionView: NSCollectionView, indexPath: IndexPath, item: SourceInfo) -> NSCollectionViewItem? in let playlistItem = collectionView.makeItem(withIdentifier: PlaylistItem.reuseIdentifier, for: indexPath) as? PlaylistItem playlistItem?.textField?.stringValue = item.title playlistItem?.imageView?.image = item.coverImage return playlistItem }) var snapshot = NSDiffableDataSourceSnapshot<Section, SourceInfo>() snapshot.appendSections([Section.main]) snapshot.appendItems(playlistLibrary) dataSource.apply(snapshot, animatingDifferences: false) } private func createLayout() -> NSCollectionViewLayout { let width: CGFloat = 120 let height: CGFloat = 139 let inset: CGFloat = 5 let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(width), heightDimension: .absolute(height)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset) let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(width * 4), heightDimension: .absolute(height)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset * 2, bottom: inset, trailing: inset * 2) let layout = NSCollectionViewCompositionalLayout(section: section) return layout } }
在.xib文件中设置的CollectionView尺寸对应两行四列共8个项。添加项的操作在菜单关闭(即CollectionView未显示)时进行:当项数不超过8个时,打开菜单后新增项能正常显示;但添加第9个项时,本应新增一行并支持滚动,实际却未生效。
我尝试在菜单打开后立即更新CollectionView:
override func viewDidAppear() { super.viewDidAppear() var snapshot = NSDiffableDataSourceSnapshot<Section, SourceInfo>() snapshot.appendSections([Section.main]) snapshot.appendItems(playlistLibrary) dataSource.apply(snapshot, animatingDifferences: false) collectionView.invalidateIntrinsicContentSize() }
但这并未解决问题。有趣的是,若重启扩展(从数据库读取超过8个项),CollectionView能正常显示并支持滚动。
内容的提问来源于stack exchange,提问作者Nikolai Nagornyi
相关产品推荐
相关产品推荐

