Самостоятельное изменение размера ячеек с помощью UICollectionViewCompositionalLayout
У меня есть контроллер представления, который отображает представление коллекции с ячейками с изменяющимся размером. В представлении коллекции есть одна секция, которая прокручивается по горизонтали. Выглядит это так:
Проблема
Представление коллекции ведет себя неожиданно, когда контроллер представления представлен с использованием значения по умолчанию pageSheet
style на iOS 13+.
- При перемещении листа вверх может казаться, что размер ячеек меняется, как в ячейке с надписью "Исправить" ниже:
- При вытягивании листа вверх содержимое может сместиться по горизонтали. Иногда клетки тоже могут исчезнуть:
Вопрос
Есть ли способ исправить это поведение, продолжая использовать UICollectionViewCompositionalLayout
и pageSheet
стиль презентации?
Резюме кода
Код довольно прост. Всего 3 класса, которые можно сбросить вViewController.swift
файл с использованием шаблона проекта приложения для единого просмотра в Xcode.
- А
UICollectionViewCell
класс называетсяCell
. В ячейке естьUILabel
и отменяетsizeThatFits(_:)
. - А
UIViewController
называетсяViewController
используется только для представленияBugViewController
. BugViewController
, который настраивает источник данных и представляет представление коллекции. Вот где возникает проблема.
Код
import UIKit
// MARK: - Cell -
final class Cell: UICollectionViewCell {
static let reuseIdentifier = "Cell"
lazy var label: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.frame.size = contentView.bounds.size
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(label)
contentView.backgroundColor = .tertiarySystemFill
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
.init(width: label.sizeThatFits(size).width + 32, height: 32)
}
}
// MARK: - ViewController -
final class ViewController: UIViewController {
private let button: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Tap Me!".uppercased(), for: .normal)
button.addTarget(self, action: #selector(presentBugViewController), for: .touchUpInside)
button.sizeToFit()
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.center = view.center
}
@objc func presentBugViewController() {
present(BugViewController(), animated: true)
}
}
// MARK: - BugViewController -
final class BugViewController: UIViewController {
private let models = [
"Better Call Saul",
"Mad Men",
"Rectify",
"Tiger King: Murder, Mayhem, and Madness",
"Master of None",
"BoJack Horseman"
]
private lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout())
collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.contentInset.top = 44
collectionView.backgroundColor = .white
return collectionView
}()
private lazy var dataSource = UICollectionViewDiffableDataSource<Int, String>(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as? Cell else { fatalError() }
cell.label.text = itemIdentifier
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
var snapshot = NSDiffableDataSourceSnapshot<Int, String>()
snapshot.appendSections([0])
snapshot.appendItems(models)
dataSource.apply(snapshot)
}
private func createCollectionViewLayout() -> UICollectionViewCompositionalLayout {
let layoutSize = NSCollectionLayoutSize.init(
widthDimension: .estimated(200),
heightDimension: .absolute(32)
)
let section = NSCollectionLayoutSection(group:
.horizontal(
layoutSize: layoutSize,
subitems: [.init(layoutSize: layoutSize)]
)
)
section.interGroupSpacing = 8
section.orthogonalScrollingBehavior = .continuous
return .init(section: section)
}
}
Заметки
- Представление коллекции в моем приложении на самом деле имеет много разделов и прокручивается по вертикали. Вот почему я использую представление коллекции с вертикальной прокруткой и раздел с
orthogonalScrollingBehavior
в примере кода.
Неудачные попытки
- Я пробовал использовать ограничения Auto Layout вместо
sizeThatFits(_:)
. - Я пробовал не использовать
UICollectionViewDiffableDataSource
.
Обходные пути
Изменение ячейки с помощью дочернего представления прокрутки и передача массива строк (в отличие от одной за раз) позволяет избежать этой проблемы. Но это грязный взлом, которого я бы по возможности избегал.