Интерфейс потока UICollectionViewController с читаемой шириной содержимого

У меня есть UICollectionView использующий макет потока, и я пытаюсь достичь тех же полей, что и UITableViewController с читаемой шириной содержимого.

Самое близкое, с чем я столкнулся, чтобы соответствовать этому поведению макета, - это встроить UICollectionViewController в UIViewController и иметь встроенное представление "Follow Readable Width".

UICollectionViewController embedded in UIViewController

Здесь цвет бирюзового цвета - UIViewController а цвет лосося - UICollectionViewController. Проблема в том, что UICollectionView область не прокручивает UICollectionView а индикаторы прокрутки также расположены не по краю экрана, как вы ожидаете.

Мой вопрос:

Как я могу достичь этого макета без необходимости встраивать UICollectionViewController?

Я предполагаю, что я могу как-то установить UICollectionView левого и правого разделов UICollectionView так, чтобы они соответствовали читаемым полям направляющих контента, и обновить их, переопределив viewWillTransition(to size: with coordinator:) и наблюдая уведомления UIContentSizeCategoryDidChange, но я не уверен, что делать об этом.

Ответ 1

Вы понимаете, правильно, самый простой способ сделать это через представление коллекции insetForSection. По моему опыту, требуется следующее:

  • Возвращает вид коллекции directionalLayoutMargins как вставка (использование .leading и. trailing
  • Отключить автоматическую обработку безопасной области через collectionView.insetsLayoutMarginsFromSafeArea = false
  • Вручную установите вид коллекции directionalLayoutMargins используя view.readableContentGuide.layoutFrame.origin.x. Обратите внимание, что я использую view.readableContentGuide, а не collectionView.
  • Обновляйте directionalLayoutMargins точно таким же образом всякий раз, когда viewLayoutMarginsDidChange

Тем не менее, было бы неплохо узнать, как это сделать без каких-либо контейнеров.

Ответ 2

Для того, чтобы решить проблему с Swift 5.1 и прошивкой 13, вы можете настроить макет потока sectionInsetReference свойства .fromLayoutMargins, установить коллекцию вида insetsLayoutMarginsFromSafeArea свойство false и установить представление коллекции layoutMargins свойство, чтобы соответствовать readableContentGuide врезки.

Два следующих примера кода показывают возможные реализации, чтобы иметь представление коллекции, которое имеет читаемую ширину содержимого.


# 1. Использование UICollectionViewDelegateFlowLayout

import UIKit

class CollectionViewController: UICollectionViewController {

    let inset: CGFloat = 10
    let minimumLineSpacing: CGFloat = 10
    let minimumInteritemSpacing: CGFloat = 10
    let cellsPerRow = 5

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Demo"

        guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
        layout.sectionInsetReference = .fromLayoutMargins
        collectionView.insetsLayoutMarginsFromSafeArea = false
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func viewWillLayoutSubviews() {
        let xOrigin = view.readableContentGuide.layoutFrame.origin.x
        collectionView.layoutMargins = UIEdgeInsets(top: 0, left: xOrigin, bottom: 0, right: xOrigin)
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 59
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = UIColor.orange
        return cell
    }

}
extension CollectionViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return minimumLineSpacing
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return minimumInteritemSpacing
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let marginsAndInsets = inset * 2 + collectionView.layoutMargins.left + collectionView.layoutMargins.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        return CGSize(width: itemWidth, height: itemWidth)
    }

}

# 2. Подклассы UICollectionViewFlowLayout

ColumnFlowLayout.swift

import UIKit

class ColumnFlowLayout: UICollectionViewFlowLayout {

    let cellsPerRow: Int

    init(cellsPerRow: Int, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
        self.cellsPerRow = cellsPerRow
        super.init()

        self.sectionInsetReference = .fromLayoutMargins
        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepare() {
        super.prepare()

        guard let collectionView = collectionView else { return }
        let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.layoutMargins.left + collectionView.layoutMargins.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        itemSize = CGSize(width: itemWidth, height: itemWidth)
    }

}

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let columnLayout = ColumnFlowLayout(
        cellsPerRow: 5,
        minimumInteritemSpacing: 10,
        minimumLineSpacing: 10,
        sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Demo"

        collectionView.collectionViewLayout = columnLayout
        collectionView.insetsLayoutMarginsFromSafeArea = false
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func viewWillLayoutSubviews() {
        let xOrigin = view.readableContentGuide.layoutFrame.origin.x
        collectionView.layoutMargins = UIEdgeInsets(top: 0, left: xOrigin, bottom: 0, right: xOrigin)
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 59
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = UIColor.orange
        return cell
    }

}

Показать на iPhone Xs Max:

enter image description here