*2021 年 *注意事项! 请参阅@Ely 的答案UICollectionLayoutListConfiguration!!!!
UICollectionLayoutListConfiguration
在垂直方向UICollectionView上,
UICollectionView
是否可以有 全角单元格 ,但是,允许 动态高度 由 自动布局 控制?
这让我觉得这可能是“iOS 中最重要的问题,但没有真正好的答案”。
请注意,在 99% 的情况下,要实现全宽单元格 + 自动布局动态高度,只需 使用表格视图。 就这么容易。
集合视图比表视图强大得多。
一个简单的示例,您必须使用具有自动布局动态高度的集合视图:
如果您在集合视图中的两个布局之间进行 动画处理。 例如,在 1 和 2 列布局之间,当设备旋转时。
这是iOS中的一个常见习语。不幸的是,它只能通过解决此 QA 中提出的问题来实现。:-/
在 Swift 5.1 和 iOS 13 中,您可以使用Compositional Layout 对象来解决您的问题。
以下完整示例代码显示了如何UILabel在 full-width 内显示多行UICollectionViewCell:
UILabel
UICollectionViewCell
CollectionViewController.swift
import UIKit class CollectionViewController: UICollectionViewController { let items = [ [ "Lorem ipsum dolor sit amet.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", ], [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", ], [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.", ] ] override func viewDidLoad() { super.viewDidLoad() let size = NSCollectionLayoutSize( widthDimension: NSCollectionLayoutDimension.fractionalWidth(1), heightDimension: NSCollectionLayoutDimension.estimated(44) ) let item = NSCollectionLayoutItem(layoutSize: size) let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1) let section = NSCollectionLayoutSection(group: group) section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10) section.interGroupSpacing = 10 let headerFooterSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(40) ) let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem( layoutSize: headerFooterSize, elementKind: "SectionHeaderElementKind", alignment: .top ) section.boundarySupplementaryItems = [sectionHeader] let layout = UICollectionViewCompositionalLayout(section: section) collectionView.collectionViewLayout = layout collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell") collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView") } override func numberOfSections(in collectionView: UICollectionView) -> Int { return items.count } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return items[section].count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell cell.label.text = items[indexPath.section][indexPath.row] return cell } override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView headerView.label.text = "Header" return headerView } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { context in self.collectionView.collectionViewLayout.invalidateLayout() }, completion: nil) } }
HeaderView.swift
import UIKit class HeaderView: UICollectionReusableView { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .magenta addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
CustomCell.swift
import UIKit class CustomCell: UICollectionViewCell { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) label.numberOfLines = 0 backgroundColor = .orange contentView.addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
预期显示:
使用 Swift 5.1 和 iOS 11,您可以子类化UICollectionViewFlowLayout并将其estimatedItemSize属性设置为UICollectionViewFlowLayout.automaticSize(这告诉系统您要处理 autoresizing UICollectionViewCells)。然后,您必须覆盖layoutAttributesForElements(in:)并layoutAttributesForItem(at:)设置单元格宽度。最后,您必须覆盖单元格的preferredLayoutAttributesFitting(_:)方法并计算其高度。
UICollectionViewFlowLayout
estimatedItemSize
UICollectionViewFlowLayout.automaticSize
layoutAttributesForElements(in:)
layoutAttributesForItem(at:)
preferredLayoutAttributesFitting(_:)
以下完整代码显示了如何UILabel在全角内显示多行(受’ 安全区域和’ 插图UIcollectionViewCell约束):UICollectionView``UICollectionViewFlowLayout
UIcollectionViewCell
UICollectionView``UICollectionViewFlowLayout
import UIKit class CollectionViewController: UICollectionViewController { let items = [ [ "Lorem ipsum dolor sit amet.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", ], [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", ], [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.", ] ] let customFlowLayout = CustomFlowLayout() override func viewDidLoad() { super.viewDidLoad() customFlowLayout.sectionInsetReference = .fromContentInset // .fromContentInset is default customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize customFlowLayout.minimumInteritemSpacing = 10 customFlowLayout.minimumLineSpacing = 10 customFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) customFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40) collectionView.collectionViewLayout = customFlowLayout collectionView.contentInsetAdjustmentBehavior = .always collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell") collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView") } override func numberOfSections(in collectionView: UICollectionView) -> Int { return items.count } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return items[section].count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell cell.label.text = items[indexPath.section][indexPath.row] return cell } override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView headerView.label.text = "Header" return headerView } }
CustomFlowLayout.swift
import UIKit final class CustomFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes] layoutAttributesObjects?.forEach({ layoutAttributes in if layoutAttributes.representedElementCategory == .cell { if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame { layoutAttributes.frame = newFrame } } }) return layoutAttributesObjects } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { guard let collectionView = collectionView else { fatalError() } guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else { return nil } layoutAttributes.frame.origin.x = sectionInset.left layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right return layoutAttributes } }
import UIKit class CustomCell: UICollectionViewCell { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) label.numberOfLines = 0 backgroundColor = .orange contentView.addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) layoutIfNeeded() layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) return layoutAttributes } }
以下是一些替代实现preferredLayoutAttributesFitting(_:):
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0) layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) return layoutAttributes } override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { label.preferredMaxLayoutWidth = layoutAttributes.frame.width layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height return layoutAttributes }