【问题标题】:NSCollectionView custom layout enable scrollingNSCollectionView 自定义布局启用滚动
【发布时间】:2016-06-24 14:50:49
【问题描述】:

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

这是一个截图:

这是我的布局代码:

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
}
}

【问题讨论】:

  • override var collectionViewContentSize,你能打印contentSizeself.collectionView.frame(或同化)吗?
  • contentSize: (22200.0, 30.0) frame: (0.0, 0.0, 438.0, 228.0)
  • 它垂直工作,当我将布局更改为垂直列时,我可以滚动到最后一项。始终禁用水平滚动。
  • @DasNilpferd 你有没有想过这个问题?

标签: swift macos cocoa nsscrollview nscollectionview


【解决方案1】:

此错误仍在进行中。我已经将 Bo Yuan 的讨厌的 hack 转换为 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
        }
    }
}

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

【讨论】:

  • 这可行,但是我无法水平调整窗口大小。 :(
  • 这对我有用,除了我采用最大宽度,因为 contentSize 可以小于视图边界。我可以调整窗口大小并双向滚动。
  • 不幸的是,这对我不起作用,我一直在努力在 NSCollectionView 中获得更广泛的内容视图。好奇其他人是如何做到这一点的。你为 NSScrollView 创建了什么样的自动布局属性?是否有任何其他代码来设置 contentView 的帧大小?我觉得我已经尝试了很多东西,但仍然无法让它发挥作用。
【解决方案2】:

似乎只有 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;
}

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 你能举个例子吗?我没有看到 scrollDirection 方法是 NSCollectionView 代表的一部分。
    【解决方案4】:

    我想出了一个解决方案。我创建了一个 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},然后再次设置它以使宽度与剪辑视图的宽度相同,并保持布局内容大小的高度。

    【讨论】:

    • 对 Peder:它是一个自定义的 NSCollectionViewLayout,而不是 NSCollectionViewFlowLayout。我认为您不能将 NSCollectionViewScrollDirectionHorizo​​ntal 用于自定义 NSCollectionViewLayout。
    • 可惜这不能转换成 Swift :( 让我们希望这个错误很快得到修复。
    • 我收回它,它可以通过覆盖综合属性设置器来实现 Swift 化。
    • 感谢您的快速代码,Ash。我认为它可能与 Mac OS 上的 AutoLayout 有关,因为框架被重置为 {0, 0} 总是与剪辑视图相同。希望有人能找到更好的解决方案,或者如果它实际上是一个错误,Apple 的人可以修复它。
    【解决方案5】:

    简答

    确保您的自定义布局类响应 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,但我无法弄清楚如何启用这些调试消息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-02
      • 1970-01-01
      相关资源
      最近更新 更多