9

I have an NSCollectionView that contains a collection of CustomViews. Initially it tiled the subviews into columns and rows like a grid. I then set the Columns property in IB to 1, so now it just displays them one after another in rows. However, even though my CustomView is 400px wide, it's set to autoresize, the NSCollectionView is 400px wide, and it's set to 1 column, the subviews are drawn about 80px wide.

I know I can get around this by calling:

CGFloat width = collectionView.bounds.size.width;
NSSize size = NSMakeSize(width, 85);
[collectionView setMinItemSize:size];
[collectionView setMaxItemSize:size];

But putting this code in the awakeFromNib method of my WindowController only sets the correct width when the program launches. When I resize the window (and the NSCollectionView autoresizes as I've specified), the CustomViews stay at their initially set width.

I'm happy to take care of resizing the subviews myself if need be, but I'm quite new to Cocoa and can't seem to find any articles explaining how to do such a thing. Can someone point me in the right direction?

Anthony

Andriy
  • 2,767
  • 2
  • 21
  • 29
littlecharva
  • 4,224
  • 8
  • 45
  • 52
  • Have now discovered that I can set my WindowController to be the Window's delegate and receive resize notifications via windowDidResize. So I'm now using the code above each time the window resizes to set the Max and Min Item Sizes. This works fine as the window grows, but for some reason increasing the Min/Max Item sizes causes the collectionView's bounds to never shrink, even when the window does. So even though the CollectionView visually shrinks as I resize the window, it's bounds never does, and there for my subviews don't either! – littlecharva Jan 21 '10 at 22:04
  • Did you ever find a way to do this automatically? – tofutim Feb 25 '14 at 16:37

8 Answers8

9

The true answer is to set the maxItemSize to 0,0(NSZeroSize). Otherwise, it is computed.

[self.collectionView setMaxItemSize:NSZeroSize];

This can be set in awakeFromNib.

Andriy
  • 2,767
  • 2
  • 21
  • 29
tofutim
  • 22,664
  • 20
  • 87
  • 148
  • This is so simple and it does exactly what I needed. The items in my collection view now resize with the collection view just like I was intending. – Chris May 28 '14 at 02:08
3

I know this is a very late response but I got the same problem and hope my solution will help somebody. Solution is to access bounds of enclosing scroll view not of collection view itself. So to solve it you need to replace first line with:

CGFloat width = collectionView.enclosingScrollView.bounds.size.width;
Andriy
  • 2,767
  • 2
  • 21
  • 29
Nikita
  • 79
  • 6
  • 1
    Where is the width set? – tofutim Feb 25 '14 at 16:05
  • I tried setting it with awakeFromNib - about 60% of time, this works - but strangely in the other 40% of the time the enclosingScrollView has 0 width. I feel like XCode is somehow caching something and it ends up restoring the nib without building it properly. – tofutim Feb 27 '14 at 16:04
3

I couldn't get this to work with a default layout - but it is fairly easy to implement a custom layout:

/// Simple single column layout, assuming only one section
class SingleColumnLayout: NSCollectionViewLayout {

    /// Height of each view in the collection
    var height:CGFloat = 100

    /// Padding is wrapped round each item, with double an extra bottom padding above the top item, and an extra top padding beneath the bottom
    var padding = EdgeInsets.init(top: 5, left: 10, bottom: 5, right: 10)

    var itemCount:Int {
        guard let collectionView = collectionView else {
            return 0
        }

        return collectionView.numberOfItems(inSection:0)
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
        return true
    }

    override open func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? {
        let attributes = NSCollectionViewLayoutAttributes(forItemWith: indexPath)
        guard let collectionView = collectionView else {
            return attributes
        }

        let bounds = collectionView.bounds
        let itemHeightWithPadding = height + padding.top + padding.bottom
        let row = indexPath.item

        attributes.frame = NSRect(x: padding.left, y: itemHeightWithPadding * CGFloat(row) + padding.top + padding.bottom , width: bounds.width - padding.left - padding.right , height: height)
        attributes.zIndex = row

        return attributes
    }

    //If you have lots of items, then you should probably do a 'proper' implementation here
    override open func layoutAttributesForElements(in rect: NSRect) -> [NSCollectionViewLayoutAttributes] {
        var attributes = [NSCollectionViewLayoutAttributes]()

        if (itemCount>0){
            for index in 0...(itemCount-1) {
                if let attribute = layoutAttributesForItem(at: NSIndexPath(forItem: index, inSection: 0) as IndexPath) {
                    attributes.append(attribute)
                }
            }
        }

        return attributes
    }

    override open var collectionViewContentSize: NSSize {

        guard let collectionView = collectionView else {
            return NSSize.zero
        }

        let itemHeightWithPadding = height + padding.top + padding.bottom

        return NSSize.init(width: collectionView.bounds.width, height: CGFloat(itemCount) * itemHeightWithPadding + padding.top + padding.bottom )
    }
}

then all you need is

var layout=SingleColumnLayout()
collectionView.collectionViewLayout = layout
Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49
1

another late one - I just switched to using an NSTableView and providing an NSView by the delegate method.

Autoresizing comes for free, one column is easy, and it renders massively faster.

Andriy
  • 2,767
  • 2
  • 21
  • 29
Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49
0

Lets say you want your CollectionViewItem with a size of 200x180px, then you should set:

[myCollectionView setMinItemSize:NSMakeSize(200, 180)];
[myCollectionView setMaxItemSize:NSMakeSize(280, 250)];

Your Max-Size should be big enough to look good and give enough space for stretching to fit the collectionView-Width.

If you have a fixed number of columns, you can probably use (0,0), but if you want a dynamic number of rows and columns like I wanted.. you should set a fixed min-size and a bigger max.size.

Rikco
  • 376
  • 5
  • 9
0

While you might get a collection view to behave as you want, I think you have a design problem

You should use a NSTableView and set columns to 1 and their sizing to anything but "None". NSTableView is intended for tabular data, plus it can recycle cells which gives a great performance boost for large amount of items. NSCollectionView is more like a linear layout which arranges items in a grid, with vertical scrolling. It is useful when the column number can change dynamically to allow more items to be shown on screen, usually depending on device orientation and screen size.

Andriy
  • 2,767
  • 2
  • 21
  • 29
Radu Simionescu
  • 4,518
  • 1
  • 35
  • 34
  • There are 5% cases where it is not a design issue. My case NSTableView doesn't like NSDatePicker its rows. NSOutlineView works out of the box. – Marek H Dec 27 '18 at 13:11
0

I tried all of solutions proposed here and none of them helped. If you use flow layout (it's used by default) you can extend it with the following code and delegate's sizeForItem method will be called on each change

class MyCollectionViewFlowLayout: NSCollectionViewFlowLayout {

    override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
        return true
    }

    override func invalidationContext(forBoundsChange newBounds: NSRect) -> NSCollectionViewLayoutInvalidationContext {
       let context = super.invalidationContext(forBoundsChange: newBounds) as! NSCollectionViewFlowLayoutInvalidationContext
       context.invalidateFlowLayoutDelegateMetrics = true
       return context
    }
}

Hope it helps someone as it took me couple of evenings to find solution

zaplitny
  • 416
  • 4
  • 19
-5

Matt Gallagher's wonderful blog Cocoa with Love is about to address this. This week, he shared the bindings details of a one-column view like the one in question:

http://cocoawithlove.com/2010/03/designing-view-with-bindings.html

Next entry, he promises to share the rest of the implementation, which should give you what you're looking for.

(I should note that he is subclassing NSView directly and re-implementing many of NSCollectionView's features. This seems to be common though; not many people are fond of subclassing NSCollectionView.)

Edit: Seems he broke his promise... we never did receive that post. See tofutim's answer below instead.

andyvn22
  • 14,696
  • 1
  • 52
  • 74