Как правильно установить вставку раздела в подклассе UICollectionViewFlowLayout с ячейками динамического размера - PullRequest
0 голосов
/ 09 марта 2019

Моя цель - иметь ячейки динамического размера в UICollectionView.Кроме того, когда прокручиваемый contentWidth из UICollectionView меньше, чем bounds из Коллекции, я хочу, чтобы элементы были в центре коллекции.

Goal

Подход к настоящему моменту:

Я пытаюсь сделать это максимально чистым способом.У меня есть собственный размер UICollectionViewCells, где Auto-Layout может определить размер ячейки на основании контентов контента.Для этого мои ячейки переопределяют функцию preferredLayoutAttributesFitting, возвращая соответствующую ширину для их содержимого.

Мой пользовательский подкласс UICollectionViewFlowLayout использует эту информацию о ширине, предоставленную UICollectionViewCell, для правильного размера ячеек.

Единственные методы, переопределенные в моем пользовательском UICollectionViewFlowLayout:

  • layoutAttributesForItem(at...)
  • layoutAttributesForElementsIn(rect...)
  • shouldInvalidateLayout(forBoundsChange...)

Динамический размер UICollectionViewCells работает безупречно.

Проблема:

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

collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets

Проблема заключается в том, что метод inset вызывается на ранней стадии процесса компоновки, где я не знаю, что contentSize этобудет определен во время макета.

Вот набор различных методов макета для одного полного прохода макета в порядке их запуска:

prepare started
prepare finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
prepare started
insetForSectionAt 0 called <--- The datasource has knowledge of the presence of cells but the cells themselves are not yet dequeued
prepare finished
layoutAttributesForElementsInRect started
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
prepare started
prepare finished
layoutAttributesForElementsInRect started
<--- At this point Cells DO return valid sizes through Autolayout
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished

Для одного прохода макета:выполняется несколько вызовов различных методов макета, но только в самом конце информация о размере из Auto-layout доступна из разных ячеек для передачи в макет.

Я заметил, что функция делегата sectionInsetвызывается рано, до того, как ячейки были должным образом удалены из очереди и им разрешено запускать ограничения Autolayout.Следовательно, невозможно сообщить правильное значение для вставки во время ее вызова.

При таком подходе можно ли принудительно вызвать вызов для пересчета sectionInsetAt после прохода макета или следуетЯ полностью изменил технику?

То, что я пробовал:

Я пытался запросить collectionViewLayout collectionViewContentSize из функции делегата insetForSectionAt.К сожалению, это не может быть использовано во время этапа макета, так как макет в данный момент выполняется.Вызывает сбой с ошибкой:

[CollectionView] An attempt to update layout information was detected while already in the process of computing the layout (i.e. reentrant call). This will result in unexpected behaviour or a crash. This may happen if a layout pass is triggered while calling out to a delegate. UICollectionViewFlowLayout instance is (<App.ViewportToolbarFlowLayout: 0x7f8a5261add0>)

Я пытался использовать метод collectionView contentSize, чтобы получить размер во время выполнения метода делегата insetForSectionAt, но он не обеспечиваетправильные данные в тот момент, когда они выполняются в процессе компоновки.

1 Ответ

0 голосов
/ 09 марта 2019

Размещение ответа здесь.Я нашел этот ответ Алекса Коши, который мне очень помог.

В основном, работа должна быть выполнена в layoutAttributesForElementsIn(rect...), поскольку это единственное место, где полный макет изображениякогда-либо в наличии.Я адаптировал код Алекса для обработки как горизонтальной, так и вертикальной UICollectionViews (что, возможно, не то, что все хотят, но то, что мне нужно).Я использую свойство scrollDirection макета, чтобы определить, будут ли элементы центрированы по горизонтали (когда scrollDirection равно .horizontal) или по вертикали (когда scrollDirection равно .vertical).

Воткод:

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    guard let collectionView = collectionView else { return nil }

    guard let superLayoutAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
    let computedAttributes = superLayoutAttributes.compactMap { layoutAttribute -> UICollectionViewLayoutAttributes? in
        return layoutAttribute.representedElementCategory == .cell ? layoutAttributesForItem(at: layoutAttribute.indexPath) : layoutAttribute
    }
    guard let attributes = NSArray(array: computedAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }

    let interItemSpacing = minimumInteritemSpacing

    switch scrollDirection {
    case .horizontal:
        let leftPadding: CGFloat = self.sectionInset.left
        var leftMargin: CGFloat = leftPadding // Modified to determine origin.x for each item
        var maxY: CGFloat = -1.0 // Modified to determine origin.y for each item
        var rowSizes: [[CGFloat]] = [] // Tracks the starting and ending x-values for the first and last item in the row
        var currentRow: Int = 0 // Tracks the current row
        attributes.forEach { layoutAttribute in
            // Each layoutAttribute represents its own item
            if layoutAttribute.frame.origin.y >= maxY {

                // This layoutAttribute represents the left-most item in the row
                leftMargin = leftPadding

                currentRow += rowSizes.isEmpty ? 0 : 1
                // Register its origin.x in rowSizes for use later
                rowSizes.append([leftMargin, 0])
            }
            layoutAttribute.frame.origin.x = leftMargin
            leftMargin += layoutAttribute.frame.width + interItemSpacing
            maxY = max(layoutAttribute.frame.maxY, maxY)
            // Add right-most x value for last item in the row
            rowSizes[currentRow][1] = leftMargin - interItemSpacing
        }
        // At this point, all cells are left aligned
        // Reset tracking values and add extra left padding to center align entire row
        leftMargin = leftPadding
        maxY = -1.0
        currentRow = 0
        attributes.forEach { (layoutAttribute) in
            if layoutAttribute.frame.origin.y >= maxY {

                // This layoutAttribute represents the left-most item in the row
                leftMargin = leftPadding

                // Need to bump it up by an appended margin
                let rowWidth = rowSizes[currentRow][1] - rowSizes[currentRow][0] // last.x - first.x
                let appendedMargin = (collectionView.frame.width - leftPadding  - rowWidth - leftPadding) / 2
                leftMargin += appendedMargin

                currentRow += 1
            }

            layoutAttribute.frame.origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + interItemSpacing
            maxY = max(layoutAttribute.frame.maxY, maxY)
        }

    case .vertical:
        let topPadding: CGFloat = self.sectionInset.top
        var topMargin: CGFloat = topPadding
        var maxX: CGFloat = -1.0 // Modified to determine origine.x for each item
        var colSizes: [[CGFloat]] = [] // Tracks the starting and ending y-values for the first and last item in the column
        var currentCol: Int = 0
        attributes.forEach { (layoutAttribute) in
            if layoutAttribute.frame.origin.x >= maxX {
                topMargin = topPadding

                currentCol += colSizes.isEmpty ? 0 : 1
                colSizes.append([topMargin, 0])
            }

            layoutAttribute.frame.origin.y = topMargin
            topMargin += layoutAttribute.frame.height + interItemSpacing
            maxX = max(layoutAttribute.frame.maxX, maxX)
            colSizes[currentCol][1] = topMargin - interItemSpacing
        }
        topMargin = topPadding
        maxX = -1.0
        currentCol = 0
        attributes.forEach { (layoutAttribute) in
            if layoutAttribute.frame.origin.x >= maxX {

                topMargin = topPadding

                let colWidth = colSizes[currentCol][1] - colSizes[currentCol][0]
                let appendedMargin = (collectionView.frame.height - topPadding - colWidth - topPadding) / 2
                topMargin += appendedMargin

                currentCol += 1
            }

            layoutAttribute.frame.origin.y = topMargin

            topMargin += layoutAttribute.frame.height + interItemSpacing
            maxX = max(layoutAttribute.frame.maxX, maxX)
        }
    }

    return attributes
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...