【问题标题】:How to implement a NSCollectionView with centered items which have self-resizing margins?如何使用具有自调整边距的居中项目实现 NSCollectionView?
【发布时间】:2019-12-16 06:05:20
【问题描述】:

假设我在集合视图中有一个项目,该项目将在第一行的集合视图中居中。

如果有多个项目,所有这些项目将在集合视图中水平分布,它们之间有适当的间距。

如果集合视图的大小发生变化,项目之间的间距将同时更改以适应集合视图的新大小。

NSCollectionView 的默认行为是左对齐项目,多个项目之间没有间距。

我应该使用集合视图层的layoutManager 来布局项目吗?

由于我是使用数据绑定来提供项,所以插入约束似乎并不容易。

【问题讨论】:

    标签: macos cocoa nscollectionview nscollectionviewitem


    【解决方案1】:

    最直接的方法是继承NSCollectionViewFlowLayout。该布局几乎是您想要的 - 它始终具有您正在寻找的相同数量的行和每行项目:您只是希望它们居中。

    主要思想是获取NSCollectionViewFlowLayout为每个项目提供的框架,从总宽度中减去这些宽度,然后更新框架,使它们均匀分布。

    作为概述,这些是步骤:

    1. 覆盖 prepareLayout 以计算当前布局中的列数以及每个元素(和边缘)之间所需的空白。这是在这里完成的,因此我们只需要计算一次值。

    2. 覆盖layoutAttributesForElementsInRect。在这里,获取给定矩形中每个项目的NSCollectionViewLayoutAttributes,并根据项目所在的列和上面计算的网格间距调整原点的 x 位置。返回新属性。

    3. 覆盖 shouldInvalidateLayoutForBoundsChange 以始终返回 YES,因为我们需要在边界发生变化时重新计算所有内容。

    我有一个工作示例应用程序在这里演示:

    https://github.com/demitri/CenteringCollectionViewFlowLayout

    但这是完整的实现:

    //
    //  CenteredFlowLayout.m
    //
    //  Created by Demitri Muna on 4/10/19.
    //
    
    #import "CenteredFlowLayout.h"
    #import <math.h>
    
    @interface CenteredFlowLayout()
    {
        CGFloat itemWidth;   // width of item; assuming all items have the same width
        NSUInteger nColumns; // number of possible columns based on item width and section insets
        CGFloat gridSpacing; // after even distribution, space between each item and edges (if row full)
        NSUInteger itemCount;
    }
    - (NSUInteger)columnForIndexPath:(NSIndexPath*)indexPath;
    @end
    
    #pragma mark -
    
    @implementation CenteredFlowLayout
    
    - (void)prepareLayout
    {
        [super prepareLayout];
    
        id<NSCollectionViewDelegateFlowLayout,NSCollectionViewDataSource> delegate = (id<NSCollectionViewDelegateFlowLayout,NSCollectionViewDataSource>)self.collectionView.delegate;
        NSCollectionView *cv = self.collectionView;
    
        if ([delegate collectionView:cv numberOfItemsInSection:0] == 0)
            return;
    
        itemCount = [delegate collectionView:cv numberOfItemsInSection:0];
    
        // Determine the maximum number of items per row (i.e. number of columns)
        //
        // Get width of first item (assuming all are the same)
        // Get the attributes returned by NSCollectionViewFlowLayout, not our method override.
        NSUInteger indices[] = {0,0};
        NSCollectionViewLayoutAttributes *attr = [super layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathWithIndexes:indices length:2]];
        itemWidth = attr.size.width;
    
        NSEdgeInsets insets;
        if ([delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)])
            insets = [delegate collectionView:cv layout:self insetForSectionAtIndex:0];
        else
            insets = self.sectionInset;
    
        // calculate the number of columns that can fit excluding minimumInteritemSpacing:
        nColumns = floor((cv.frame.size.width - insets.left - insets.right) / itemWidth);
        // is there enough space for minimumInteritemSpacing?
        while ((cv.frame.size.width
                - insets.left - insets.right
                - (nColumns*itemWidth)
                - (nColumns-1)*self.minimumInteritemSpacing) < 0) {
            if (nColumns == 1)
                break;
            else
                nColumns--;
        }
    
        if (nColumns > itemCount)
            nColumns = itemCount; // account for a very wide window and few items
    
        // Calculate grid spacing
        // For a centered layout, all spacing (left inset, right inset, space between items) is equal
        // unless a row has fewer items than columns (but they are still aligned with that grid).
        //
        CGFloat totalWhitespace = cv.bounds.size.width - (nColumns * itemWidth);
        gridSpacing = floor(totalWhitespace/(nColumns+1));  // e.g.:   |  [x]  [x]  |
    }
    
    - (NSUInteger)columnForIndexPath:(NSIndexPath*)indexPath
    {
        // given an index path in a collection view, return which column in the grid the item appears
        NSUInteger index = [indexPath indexAtPosition:1];
        NSUInteger row = (NSUInteger)floor(index/nColumns);
        return (index - (nColumns * row));
    }
    
    - (NSArray<__kindof NSCollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(NSRect)rect
    {
        // We do not need to modify the number of rows/columns that NSCollectionViewFlowLayout
        // determines, we just need to adjust the x position to keep them evenly distributed horizontally.
    
        if (nColumns == 0) // prepareLayout not yet called
            return [super layoutAttributesForElementsInRect:rect];
    
        NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
        if (attributes.count == 0)
            return attributes;
    
        for (NSCollectionViewLayoutAttributes *attr in attributes) {
            NSUInteger col = [self columnForIndexPath:attr.indexPath]; // column number
            NSRect newFrame = NSMakeRect(floor((col * itemWidth) + gridSpacing * (1 + col)),
                                         attr.frame.origin.y,
                                         attr.frame.size.width,
                                         attr.frame.size.height);
            attr.frame = newFrame;
        }
    
        return attributes;
    }
    
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(NSRect)newBounds
    {
        return YES;
    }
    
    @end
    

    【讨论】:

      【解决方案2】:

      您可以创建一个 NSCollectionViewLayout 的子类并相应地实现 layoutAttributes 方法。

      【讨论】:

        猜你喜欢
        • 2012-12-08
        • 2019-10-10
        • 1970-01-01
        • 2012-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多