10.11 NSCollectionView - determining cell size dynamically

故事扮演 提交于 2019-12-03 12:28:01

问题


AppKit Release Notes for OS X v10.11 suggests that collection view items can be resized on a per-item basis:

Item size can be determined globally for all of a CollectionView’s items (by setting an NSCollectionViewFlowLayout’s “itemSize” property), or can be varied from one item to the next (by implementing -collectionView:layout:sizeForItemAtIndexPath: on your CollectionView’s delegate).

In my case, my CollectionViewItem consists of a single label that contains a string of varying length. I'm using the NSCollectionView to display an array of strings, as NSStackViews don't support array bindings, and don't flow to new lines. The array of strings is bound to the NSCollectionView's content via an array controller.

My item's nib file is properly set up, the root view and the label both have Content Hugging and Content Compression Resistance Priorities of 1000, and the edges are aligned via AutoLayout.

Now, the NSCollectionViewLayout's delegate method has this signature:

func collectionView(collectionView: NSCollectionView, 
                    layout collectionViewLayout: NSCollectionViewLayout, 
                    sizeForItemAtIndexPath indexPath: NSIndexPath) -> NSSize

My idea now is to grab the item itself, run a layout pass over it, and then returning the new item size.

let item = collectionView.itemAtIndexPath(indexPath)!
item.view.layout()
return item.view.bounds.size

Problem with this approach is that itemAtIndexPath returns nil. If I return a default size in the nil case, that default size is used for all cells.

How can I set a NSCollectionView to respect my item's AutoLayout constraints and for each cell, use the computed size dynamically?


回答1:


There's a duplicate of this question that I answered but this is probably the one it should be directed to since it's older.

Juul's comment is correct- the items do not exist. sizeForItemAt being called is the collection asking the delegate for any specific sizing for that data entry with which it will use to help create its eventual view controller, NSCollectionViewItem. So you create a loop when you ask the collection to get you an item in the method that it uses to help get an item.

The problem we have is that we want sizing based on the appearance of that data: the length of a text label with proper formatting, not just, say, the string length. So we hit a chicken and egg problem.

The only solution I've come to, which could be prettier, is the following:

Prep

  • Subclass NSCollectionViewItem and ensure your collection view has a data source that returns the proper subclassed item.
  • Use constraints in your XIB, completely.
  • Your subclass should have a method that loads in the data object to be represented- both for this and of course your data source protocol methods.
  • At some point prior to the first sizeForItemAt call, or at the beginning of the first one if you hadn't by then, manually create an instance of your NSCollectionViewItem subclass, and use NSNib's instantiate(withOwner:topLevelObjects:) to instantiate its XIB with your subclass as an owner. Store that reference as a sort of "sizing template," so you only need to do it once. Delegate was easiest spot for me.

^Note: my first route was to attempt this through the collection's makeItemWithIdentifier, but it was more brittle as it required the collection to have items at the time of creating the sizing template. It also could not be done during an initial sizeForItemAt (accessing/making items during a reload crashes). And I was worried that because it was made with the collection it may get reused down the line and the methods below don't work or start editing visible items. YMMV.

In sizeForItemAt

  • Directly get the data object being represented from the datasource. Have your sizing template object represent that data object with the method I mentioned earlier.
  • Access the sizing template's View.FittingSize, the smallest size an item can be given its constraints/priorities, and return that.

Bam! Hasn't been stress tested or anything but no problems on my end, and its not doing a layout pass or anything, just calling FittingSize. I haven't seen this articulated anywhere online yet, so I wanted to write out the full explanation.

I did this in Xamarin.Mac, so my code won't be 1:1 and I don't want to write garbled swift and mess anything up.

TLDR: manually instantiate a NSCollectionViewItem subclass and its xib that you will store, unowned by the collection. During sizeForItem populate that item you store as a sizing reference, and return the FittingSize of the collection item's view.



来源:https://stackoverflow.com/questions/33460374/10-11-nscollectionview-determining-cell-size-dynamically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!