NSCollectionView自定义布局启用滚动

时间:2016-06-24 14:50:49

标签: swift macos cocoa nsscrollview nscollectionview

我无法纵向和横向滚动以使用NSCollectionView的自定义布局。 根据文档,在我的子类中,我返回`collectionViewContentSize',如果它太大,则在集合视图的封闭滚动视图中自动启用滚动。但是,即使我在水平行中订购所有元素,也只启用垂直滚动。

以下是截图: http://i.stack.imgur.com/tMbCN.png

这是我的布局代码:

import Cocoa 
class Layout: NSCollectionViewLayout {

var cellSize = CGSize(width: 100, height: 30)

var cellSpacing: CGFloat = 10
var sectionSpacing: CGFloat = 20

private var contentSize = CGSize.zero
private var layoutAttributes = [NSIndexPath: NSCollectionViewLayoutAttributes]()


override func prepareLayout() {
    guard let collectionView = collectionView else { return }

    let sections = collectionView.numberOfSections
    guard sections > 0 else { return }

    contentSize.height = cellSize.height

    for section in 0..<sections {
        let items = collectionView.numberOfItemsInSection(section)
        guard items > 0 else { break }

        for item in 0..<items {
            let origin = CGPoint(x: contentSize.width, y: 0)
            let indexPath = NSIndexPath(forItem: item, inSection: section)
            let attributes = NSCollectionViewLayoutAttributes(forItemWithIndexPath: indexPath)
            attributes.frame = CGRect(origin: origin, size: cellSize)
            layoutAttributes[indexPath] = attributes

            contentSize.width += cellSize.width + cellSpacing
        }
        contentSize.width += sectionSpacing
    }
}

override var collectionViewContentSize: NSSize {
    return contentSize
}

override func layoutAttributesForElementsInRect(rect: NSRect) -> [NSCollectionViewLayoutAttributes] {

    return layoutAttributes.values.filter { $0.frame.intersects(rect) }
}

override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> NSCollectionViewLayoutAttributes? {
    return layoutAttributes[indexPath]
}

override func shouldInvalidateLayoutForBoundsChange(newBounds: NSRect) -> Bool {
    return false
}
}

5 个答案:

答案 0 :(得分:7)

此错误仍在进行中。我把Bo Yuan讨厌的黑客改成了Swift。 (没有冒犯,博远,这不是你的错,我们必须做这个可怕的解决方法!我仍然会在有正式修复的情况下尽快删除这个代码。暂时这只是为了让我的开发工作继续下去。)

class HackedCollectionView: NSCollectionView {
    override func setFrameSize(_ newSize: NSSize) {
        let size = collectionViewLayout?.collectionViewContentSize ?? newSize
        super.setFrameSize(size)
        if let scrollView = enclosingScrollView {
            scrollView.hasHorizontalScroller = size.width > scrollView.frame.width
        }
    }
}

请注意,这肯定需要尽快进行,因为在滚动时会为每一帧动画调用它。不太好。请,请有人解决此问题或找到合适的解决方案。不能水平滚动是荒谬的。

答案 1 :(得分:1)

这是NSCollectionView中的一个基本错误。作为一种解决方法,您可以实现scrollDirection方法(来自NSCollectionViewFlowLayout)并返回NSCollectionViewScrollDirectionHorizo​​ntal。

答案 2 :(得分:1)

我找到了解决方案。我创建了NSCollectionView的子类。并使用此子类替换默认的NSCollectionView。我创建了覆盖setFrameSize的函数。瞧!

-(void)setFrameSize:(NSSize)newSize{

    if (newSize.width != self.collectionViewLayout.collectionViewContentSize.width) {

        newSize.width = self.collectionViewLayout.collectionViewContentSize.width;

    }

    [super setFrameSize:newSize];

    //NSLog(@"setFrameSize%@", CGSizeCreateDictionaryRepresentation(newSize));

}

在我的测试中,将调用setFrameSize并且某些机制首先将帧大小设置为{0,0}然后再次设置它以使宽度与剪辑视图的宽度相同并保持高度不受布局的影响。内容大小。

答案 3 :(得分:0)

似乎只有NSCollectionViewFlowLayout才能指定一个宽度大于父滚动视图框架的框架。

解决方案是子类化NSCollectionViewFlowLayout而不是NSCollectionViewLayout。像任何其他布局子类一样处理子类,但在prepareLayout()中添加关键scrollDirection。

以下是滚动水平布局的最小实现,只是将所有项目设置为彼此相邻。

-(void) prepareLayout
{
    [super prepareLayout];
    self.scrollDirection = NSCollectionViewScrollDirectionHorizontal;
}

-(NSSize) collectionViewContentSize
{
    NSSize itemSize = self.itemSize;
    NSInteger num = [self.collectionView numberOfItemsInSection:0];
    NSSize contentSize = NSMakeSize((num * itemSize.width) + ((num+1) * self.minimumLineSpacing), NSHeight(self.collectionView.frame));
    return contentSize;
}

-(BOOL) shouldInvalidateLayoutForBoundsChange:(NSRect)newBounds { return YES; }

-(NSArray<__kindof NSCollectionViewLayoutAttributes *> *) layoutAttributesForElementsInRect:(NSRect)rect
{
    int numItems = [self.collectionView numberOfItemsInSection:0];
    NSMutableArray* attributes = [NSMutableArray arrayWithCapacity:numItems];
    for (int i=0; i<numItems; i++)
    {
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]];
    }
    return attributes;
}

-(NSCollectionViewLayoutAttributes*) layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSSize itemSize = self.itemSize;
    NSRect fr = NSZeroRect;
    fr.size = itemSize;
    fr.origin.y = (NSHeight(self.collectionView.frame) - itemSize.height) / 2.0;
    fr.origin.x = (indexPath.item+1 * self.minimumLineSpacing) + (indexPath.item * itemSize.width);
    NSCollectionViewLayoutAttributes* attr = [NSCollectionViewLayoutAttributes layoutAttributesForItemWithIndexPath:indexPath];
    attr.frame = fr;
    return attr;
}

答案 4 :(得分:0)

简答

确保您的自定义布局类响应 scrollDirection 消息(这意味着它需要暴露给 objc 运行时):

class MyCustomLayout: NSCollectionViewLayout {
    @objc var scrollDirection: NSCollectionView.ScrollDirection {
        .horizontal
    }
}

说明

花了几个小时后,我知道 NSCollectionViewLayout 类中一定有一个技巧可以让集合视图知道它应该在水平方向上调整自己的大小。在 NSCollectionView 的实现中使用、未记录和隐藏的内置布局有一个技巧(您可以在调试器中看到堆栈跟踪)。

collectionViewContentSize 方法在 NSCollectionView 的布局方法运行期间被多次调用。有一种称为 NSCollectionView._resizeToFitContentAndClipView 的私有方法。该方法的名称确实具有说明性,但如果您查看实现细节(调试器中的反汇编代码),您就会看到。一开始它获取布局类实例并检查该实例是否响应scrollDirection选择器,以及它是否根据该选择器返回的值响应该方法的行为变化。

阅读反汇编代码很有趣。由于 ObjC 的选择器名称没有被破坏,所以有很多关于这个过程的提示。此外,我认为必须有一个选项来启用特殊集合视图调试,因为它会吐出一些日志消息,例如 %@(%p): -_resizeToFitContentAndClipView found layout=%@, scrollDirection=%ld,但我不知道如何启用这些调试消息。