Пользовательский макет NSCollectionView включает прокрутку

Я не могу получить прокрутку как по вертикали, так и по горизонтали, чтобы работать с пользовательским макетом для NSCollectionView. Согласно документам, в моем подклассе я возвращаю `collectionViewContentSize', и, если он слишком велик, прокрутка автоматически включается во включающем представлении прокрутки представления собрания. Однако даже если я упорядочу все элементы в горизонтальном ряду, активируется только вертикальная прокрутка.

Вот скриншот:

Вот мой код компоновки:

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 ответов

Эта ошибка все еще продолжается. Я преобразовал мерзкий взлом Бо Юаня в Свифт. (Не обижайся, Бо Юань, это не твоя вина, что мы должны сделать этот ужасный обходной путь! Я все еще копирую этот код, как только есть официальное исправление. В настоящее время это только продолжит мои усилия по развитию.)

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

Обратите внимание, что это определенно нужно сделать как можно скорее, потому что при прокрутке он вызывается для каждого кадра анимации. Не хорошо. Пожалуйста, пожалуйста, пожалуйста, кто-нибудь или исправит эту проблему или найдет правильное решение. Неспособность прокручивать горизонтально - это смешно.

Кажется, что только NSCollectionViewFlowLayout может диктовать кадр, ширина которого больше, чем у родительского кадра прокрутки.

Решением является создание подкласса NSCollectionViewFlowLayout вместо NSCollectionViewLayout. Обрабатывайте подкласс как любой другой подкласс макета, но добавьте критическое scrollDirection в prepareLayout().

Вот минимальная реализация для макета, который прокручивает по горизонтали и просто устанавливает все элементы рядом друг с другом.

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

Это в основном ошибка в NSCollectionView. В качестве обходного пути вы можете реализовать метод scrollDirection (из NSCollectionViewFlowLayout) и вернуть NSCollectionViewScrollDirectionHor horizontal.

Короткий ответ

Убедитесь, что ваш пользовательский класс макета отвечает на сообщение (это означает, что он должен быть доступен во время выполнения objc):

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

Объяснение

Проведя часы, я понял, что в этом есть какая-то уловка. класс, чтобы представление коллекции знало, что оно должно увеличиваться в горизонтальном направлении. И есть трюк, встроенный в макеты, недокументированные и скрытые в реализации (вы можете увидеть трассировку стека в отладчике).

метод вызывается несколько раз во время метод компоновки запущен. Есть один частный метод, который называется . Название метода действительно пояснительное, но если вы посмотрите на детали реализации (дизассемблированный код в отладчике), вы увидите. Вначале он получает экземпляр класса макета и проверяет, отвечает ли экземпляр на селектор, и если он отвечает, поведение этого метода изменится в соответствии со значением, возвращаемым этим селектором.

Дизассемблированный код интересно читать. Есть много подсказок о процессе, благодаря тому, что имена селекторов ObjC не искажены. Также я думаю, что должна быть возможность включить специальную отладку представления коллекции, потому что она выдает некоторые сообщения журнала, такие как , но я не мог понять, как включить эти отладочные сообщения.

Я нашел решение. Я создал подкласс 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}, а затем снова установит его, чтобы ширина стала такой же, как у клипа, и сохранила высоту от размера содержимого макета.

Другие вопросы по тегам