1

Here is the window I want to achieve: window to achieve It is a NSViewController (RedViewController) with NSCollectionView (RedCollectionView) as view.
It item (BlueItem) has a NSCollectionView (BlueCollectionView) as view.
It item's item (GreenItem), has a NSImageView (GreenImageView) as view.
The RedCollectionView will scroll vertically, and its items will be selectable. However the BlueCollectionView is just for display, it won't scroll itself and won't be selectable.
Here is the code for the AppDelegate:

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
  lazy var redViewController: RedViewController = {
    let redViewController = RedViewController.init()
    return redViewController
  }()

  lazy var window: NSWindow = {
    let window = NSWindow.init(contentViewController: self.redViewController)
    return window
  }()

  lazy var windowController: NSWindowController = {
    let windowController = NSWindowController.init(window: self.window)
    return windowController
  }()

  func applicationDidFinishLaunching(_ aNotification: Notification) {
    self.windowController.showWindow(nil)
  }
}

Here is the code for the RedViewController:

class RedViewController: NSViewController, NSCollectionViewDataSource, NSCollectionViewDelegate {
  lazy var collectionViewLayout: NSCollectionViewFlowLayout = {
    let collectionViewLayout = NSCollectionViewFlowLayout.init()
    collectionViewLayout.itemSize = NSSize(width: 100.0, height: 100.0)
    collectionViewLayout.minimumLineSpacing = 10.0
    collectionViewLayout.minimumInteritemSpacing = 10.0
    collectionViewLayout.sectionInset = NSEdgeInsetsMake(10.0, 10.0, 10.0, 10.0)
    return collectionViewLayout
  }()

  class RedCollectionView: NSCollectionView { /* nothing here for now */ }

  lazy var redCollectionView: RedCollectionView = {
    let redCollectionView = RedCollectionView.init(frame: NSRect(x: 0.0, y: 0.0, width: 500.0, height: 500.0))
    redCollectionView.collectionViewLayout = self.collectionViewLayout
    redCollectionView.dataSource = self
    redCollectionView.delegate = self
    redCollectionView.isSelectable = true
    redCollectionView.backgroundColors = [NSColor.red]
    return redCollectionView
  }()

  override func loadView() {
    let clipView = NSClipView.init()
    clipView.documentView = self.redCollectionView
    let scrollView = NSScrollView.init(frame: NSRect(x: 0.0, y: 0.0, width: 500.0, height: 500.0))
    scrollView.contentView = clipView
    self.view = scrollView
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    self.redCollectionView.register(BlueItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier("BlueItemID"))
  }

  func numberOfSections(in collectionView: NSCollectionView) -> Int {
    return 1
  }

  func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
    return 50
  }

  func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
    let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "BlueItemID"), for: indexPath)
    guard let blueItem = item as? BlueItem else { return item }
    return blueItem
  }

  func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
    indexPaths.forEach({ print($0) })
    collectionView.deselectItems(at: indexPaths)
  }
}

Here is the code for the BlueItem:

class BlueItem: NSCollectionViewItem, NSCollectionViewDataSource {
  lazy var collectionViewLayout: NSCollectionViewFlowLayout = {
    let collectionViewLayout = NSCollectionViewFlowLayout.init()
    collectionViewLayout.itemSize = NSSize(width: 27.0, height: 27.0)
    collectionViewLayout.minimumLineSpacing = 3.0
    collectionViewLayout.minimumInteritemSpacing = 3.0
    collectionViewLayout.sectionInset = NSEdgeInsetsMake(3.0, 3.0, 3.0, 3.0)
    return collectionViewLayout
  }()

  class BlueCollectionView: NSCollectionView {
    override func mouseDown(with event: NSEvent) {
      super.mouseDown(with: event)
      self.nextResponder?.mouseDown(with: event)
    }

    override func mouseUp(with event: NSEvent) {
      super.mouseUp(with: event)
      self.nextResponder?.mouseUp(with: event)
    }
  }

  lazy var blueCollectionView: BlueCollectionView = {
    let blueCollectionView = BlueCollectionView.init(frame: NSRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
    blueCollectionView.collectionViewLayout = self.collectionViewLayout
    blueCollectionView.dataSource = self
    blueCollectionView.isSelectable = false
    blueCollectionView.backgroundColors = [NSColor.blue]
    return blueCollectionView
  }()

  override func loadView() {
    self.view = self.blueCollectionView
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    self.blueCollectionView.register(GreenItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier("GreenItemID"))
  }

  func numberOfSections(in collectionView: NSCollectionView) -> Int {
    return 1
  }

  func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
    return 7
  }

  func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
    let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "GreenItemID"), for: indexPath)
    guard let greenItem = item as? GreenItem else { return item }
    return greenItem
  }
}

And finally here is the code for the GreenItem:

class GreenItem: NSCollectionViewItem {
  class GreenImageView: NSImageView { /* nothing here for now */ }

  lazy var greenImageView: GreenImageView = {
    let image: NSImage = NSImage.init(imageLiteralResourceName: "greenImage")
    let greenImageView = GreenImageView.init(image: image)
    return greenImageView
  }()

  override func loadView() {
    self.view = self.greenImageView
  }
}

When I launch the app, I get this result: app screenshot

So I have 3 issues:
1. The BlueItem with it BlueCollectionView don't get the blue background expected.
2. The BlueCollectionView flow layout tries to display all the items on the first row. It looks like it doesn't know the collection view size.
3. When I try to select a BlueItem: if I click on an empty space inside the BlueItem, the RedCollectionView delegate is triggered as expected, but if I click on a green part inside the BlueItem, the RedCollectionView delegate isn't triggered.

nouatzi
  • 735
  • 4
  • 14
  • 1
    Similar question: [NSCollectionViewItem behind a NSTextField](https://stackoverflow.com/questions/9436259/nscollectionviewitem-behind-a-nstextfield). – Willeke Jul 09 '19 at 08:32
  • 1
    So I've subclassed the parent collectionview just to check if it get to mouse event: class CollectionView: NSCollectionView { override func mouseDown(with event: NSEvent) { print(event); super.mouseDown(with: event) } So it does get the event, but doesn't trigger its delegate. However mouseUp isn't triggered. – nouatzi Jul 09 '19 at 10:52
  • Ok, so the child collectionview was actually in a NSStackView with a NSTextField label. So by propagating the mouseUp event to the Stackview, it actually worked. Thanks Willeke. – nouatzi Jul 09 '19 at 11:01
  • 1
    If I click inside the child collectionview - in an empty space - it works. But if I click inside the child collectionview - on one of it items - it doesn't work. However both mouseDown and mouseUp events are received by the parent collectionview. – nouatzi Jul 09 '19 at 11:54
  • I have a similar problem where it appears that my NSCollectionView is eating all `mouseUp` events whenever I click on an item (but not when clicking on empty space). Did you ever find a solution to get the mouseUp events up to the responder chain? – cdf1982 May 15 '23 at 10:31

0 Answers0