Get index of a view inside a NSCollectionView?

3.4k views Asked by At

I've developed an app for Mac OS X Lion using its new view-based NSTableView, but as I want to port the whole app to Snow Leopard I'm trying to figure out the best way to emulate such a tableview. So far I've created a NSCollectionView and everything is fine, except for the fact that I can't get the index of the view from which a button click event is triggered. In Lion I have the following function:

- (IBAction)buttonClick:(id)sender

so I can get the index of the view inside the tableview using a method (I can't remember its name) like

- (NSInteger)rowForView:(NSView *)aView

with aView being the sender's superview, but I couldn't find something similar for the collection view ... The only "useful" method seems to be

- (NSCollectionViewItem *)itemAtIndex:(NSUInteger)index

(or something like this), but this can't help me as it returns a NSCollectionViewItem and I can't even access it knowing only the corresponding view!

6

There are 6 answers

2
Devarshi On BEST ANSWER

Within buttonClick, try this code:

id collectionViewItem = [sender superview];
NSInteger index = [[collectionView subviews]  indexOfObject:collectionViewItem];
return index;

Hope this helps :)

0
Dom On

As I suggested here: How to handle a button click from NSCollectionView

I would do it like this (because the button you want to press should be coupled with the corresponding model, therefore the represented object):

  1. Add a method to the model of your collectionViewItem (e.g. buttonClicked)
  2. Bind the Button Target to Collection View Item
  3. While binding set model key path to: representedObject
  4. While binding set selectorname to: methodname you chose earlier (e.g. buttonClicked)
  5. Add protocol to your model, if you must tell delegate or establish observer-pattern
1
Dave DeLong On

How about something like:

id obj = [collectonViewItem representedObject];
NSInteger index = [[collectionView contents] indexOfObject:obj];
0
Gordon Apple On

Geesh! Both of those approaches have issues. I can see how the first on may work, but note that the "collectionViewItem" is actually the view, NOT the collectionViewItem, which is a view controller.

The second way will not work, unless you subclass the button and put in a back link to the collectionViewItem. Otherwise, your view does not know what collectionViewItem controls it. You should use a selector binding to the collectionViewItem's representedObject instead, to get the action to the correct object in your array.

0
kimimaro On
  1. use NSArrayController for binding to NSCollectionView,

  2. use collectonViewItem.representedObject to get a Custom Model defined by yourself.

  3. save and get index in your custom model.

That's works for me.

0
DarkDust On

Here's one way to solve this:

  • For the collection view item, create a dedicated NSView subclass and use this as the main container class in the collection view item XIB. By this I mean the view property of your NSCollectionViewItem instance should point to this dedicated class.
  • Then, in you button action, you need to find super views of the button:
    • The dedicated container class, mentioned above.
    • The collection view.
    • (Your view hierarchy will roughly be something like collection view > item view > button, so these two views should always exist, unless the container is not visible.)
  • When you got all this information, you can iterate over the collection views's visibleItems. The item you're looking for is the one with item.view == yourContainerView.

First, let's create a helper method on NSView:

extension NSView {
    
    /// The receiver or one of its super views matching the given class.
    func enclosingView<T>(type: T.Type) -> T? {
        var cursor: NSView? = self
        
        while let current = cursor {
            if let match = cursor as? T {
                return match
            }
            
            cursor = current.superview
        }
        
        return nil
    }
    
}

Using this, the search for the item works like this:

let button: NSButton = yourButtonInstance
guard
    let container = button.enclosingView(type: YourContainer.self), 
    let collectionView = button.enclosingView(type: NSCollectionView.self),
    let item = collectionView.visibleItems().first(where: { $0.view == container })
else {
    return
}

/// `item` now is the NSCollectionViewItem instance associated with your button.