Reputation: 1461
I want to put an NSCollectionView inside a menu. It's pretty simple:
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)
}
The 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
}
}
The size of the CollectionView set in the .xib file means two rows of four items. Adding items to the CollectionView happens when the menu is closed, meaning the CollectionView is not on the screen. As long as the number of elements does not exceed eight, everything is fine, when you open the menu, the added elements are displayed. Adding the 9th item should add a new row and allow the CollectionView to scroll, but it doesn't. I was trying to update the CollectionView right after opening my menu:
override func viewDidAppear() {
super.viewDidAppear()
var snapshot = NSDiffableDataSourceSnapshot<Section, SourceInfo>()
snapshot.appendSections([Section.main])
snapshot.appendItems(playlistLibrary)
dataSource.apply(snapshot, animatingDifferences: false)
collectionView.invalidateIntrinsicContentSize()
}
but that didn't solve the problem. Interestingly, if the extension is restarted (when reading the playlist library from the database with more than 8 items), the CollectionView is displayed correctly and scrolling is possible. Please help me solve this problem. I've already broken my brain.
Upvotes: 0
Views: 66