Есть несколько шагов для достижения этой цели:
- Определение собственной величины
UICollectionViewCell
- Определение размера
UICollectionView
- Определение собственной величины
UITableViewCell
- Рассчитайте все размеры ячеек и найдите самые высокие (может быть дорого)
- Перезагрузка высшего порядка для любого ребенка
bounds
изменено
- Определение размера UICollectionViewCell
У вас есть два варианта:
- использовать авторазметку с автоматически изменяющимися элементами пользовательского интерфейса (например, UIImageView
или UILabel
или т. д.)
- посчитайте и предоставьте свой размер
-
Использовать авторазметку с автоматически изменяющимися элементами пользовательского интерфейса:
Если вы установите estimatedItemsSize
равным automatic
и правильно разместите подпредставления ячеек с помощью элементов пользовательского интерфейса с изменяемым размером, оно будет автоматически изменяться на лету. НО следует учитывать дорогое время компоновки и задержки из-за сложных макетов. Специально на старых устройствах.
-
Рассчитайте и укажите его размер:
Одним из лучших мест, где вы можете настроить размер ячейки, является intrinsicContentSize
. Хотя для этого есть и другие места (например, в классе flowLayout и т. Д.), Здесь вы можете гарантировать, что ячейка не будет конфликтовать с другими элементами пользовательского интерфейса с изменяющимся размером:
override open var intrinsicContentSize: CGSize { return /*CGSize you prefered*/ }
- Определение размера UICollectionView
Любой подкласс UIScrollView
может быть самодостаточным дуэтом до contentSize
. Поскольку размеры содержимого collectionView и tableView постоянно меняются, вы должны сообщить layouter
(официально нигде не известен как layouter):
protocol CollectionViewSelfSizingDelegate: class {
func collectionView(_ collectionView: UICollectionView, didChageContentSizeFrom oldSize: CGSize, to newSize: CGSize)
}
class SelfSizingCollectionView: UICollectionView {
weak var selfSizingDelegate: CollectionViewSelfSizingDelegate?
override open var intrinsicContentSize: CGSize {
return contentSize
}
override func awakeFromNib() {
super.awakeFromNib()
if let floawLayout = collectionViewLayout as? UICollectionViewFlowLayout {
floawLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
} else {
assertionFailure("Collection view layout is not flow layout. SelfSizing may not work")
}
}
override open var contentSize: CGSize {
didSet {
guard bounds.size.height != contentSize.height else { return }
frame.size.height = contentSize.height
invalidateIntrinsicContentSize()
selfSizingDelegate?.collectionView(self, didChageContentSizeFrom: oldValue, to: contentSize)
}
}
}
Таким образом, после переопределения intrinsicContentSize
мы наблюдаем за изменениями contentSize и сообщаем selfSizingDelegate
, что необходимо знать об этом.
- Определение размера UITableViewCell
Эта ячейка может быть саморегулируемой, если реализовать это в своих tableView
:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
Мы поместим selfSizingCollectionView
в эту ячейку и осуществим это:
class SelfSizingTableViewCell: UITableViewCell {
@IBOutlet weak var collectionView: SelfSizingCollectionView!
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
return collectionView.frame.size
}
override func awakeFromNib() {
...
collectionView.selfSizingDelegate = self
}
}
Итак, мы соответствуем делегату и реализуем функцию обновления ячейки.
extension SelfSizingTableViewCell: CollectionViewSelfSizingDelegate {
func collectionView(_ collectionView: UICollectionView, didChageContentSizeFrom oldSize: CGSize, to newSize: CGSize) {
if let indexPath = indexPath {
tableView?.reloadRows(at: [indexPath], with: .none)
} else {
tableView?.reloadData()
}
}
}
Обратите внимание, что я написал несколько вспомогательных расширений для UIView
и UITableViewCell
для доступа к представлению родительской таблицы:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
extension UITableViewCell {
var tableView: UITableView? {
return (next as? UITableView) ?? (parentViewController as? UITableViewController)?.tableView
}
var indexPath: IndexPath? {
return tableView?.indexPath(for: self)
}
}
К этому моменту ваше представление коллекции будет иметь точно необходимую высоту (если оно вертикальное), но вы должны помнить об ограничении ширины всех ячеек, чтобы не выходить за пределы ширины экрана.
extension SelfSizingTableViewCell: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width, height: heights[indexPath.row])
}
}
Уведомление heights
? Здесь вы должны кэшировать (вручную / автоматически) вычисленную высоту всех ячеек, чтобы не отставать. (Я генерирую их все случайным образом для тестирования)
- Последние два шага:
Последние два шага уже пройдены, но вы должны рассмотреть некоторые важные вещи, которые вы ищете:
1 - Autolayout может быть эпическим убийцей производительности, если вы попросите его рассчитать все размеры ячеек для вас, но это надежно.
2 - Вы должны кешировать все размеры, получить самое высокое и вернуть его в tableView(:,heightForRowAt:)
(больше не нужно определять размер tableViewCell
)
3 - Считайте, что самый высокий из них может быть выше целого tableView
, а 2D прокрутка будет странной.
4 - Если вы повторно используете ячейку, содержащую collectionView
или tableView
, смещение прокрутки, вставка и многие другие атрибуты ведут себя странно так, как вы этого не ожидаете.
Это один из самых сложных макетов из всех, которые вы могли встретить, но как только вы привыкнете к нему, он станет простым.