这里有一个优雅、最新的解决方案。
正如其他人所说,首先确保您拥有的所有约束都从您的标题视图顶部开始,直到第一个子视图的顶部,从第一个子视图的底部到第二个子视图的顶部等等,并且从最后一个子视图的底部到您的标题视图的底部。只有这样自动布局才能知道如何调整视图的大小。
以下代码片段返回计算出的标题视图大小。
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
// Get the view for the first header
let indexPath = IndexPath(row: 0, section: section)
let headerView = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: indexPath)
// Use this view to calculate the optimal size based on the collection view's width
return headerView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height),
withHorizontalFittingPriority: .required, // Width is fixed
verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed
}
正如@Caio在评论中指出的那样,这种解决方案会导致iOS 10及更早版本崩溃。
在我的项目中,我通过将上面的代码包装在if #available(iOS 11.0, *) { ... }
中并在else子句中提供固定大小来“解决”这个问题。虽然不是最理想的,但在我的情况下是可以接受的。
collectionView(collectionView:kind:at)
之前取消排队补充视图。这将导致崩溃。 - Caio这让我抓狂了半天,以下是最终行得通的方法:
确保标题标签设置为动态调整大小、单行和换行
class CustomHeader: UICollectionReusableView {
@IBOutlet weak var contentView: UIView!
}
使初始布局失效
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
布局标题以获得正确的大小
extension YourViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionElementKindSectionHeader).first as? CustomHeader {
// Layout to get the right dimensions
headerView.layoutIfNeeded()
// Automagically get the right height
let height = headerView.contentView.systemLayoutSizeFitting(UILayoutFittingExpandedSize).height
// return the correct size
return CGSize(width: collectionView.frame.width, height: height)
}
// You need this because this delegate method will run at least
// once before the header is available for sizing.
// Returning zero will stop the delegate from trying to get a supplementary view
return CGSize(width: 1, height: 1)
}
}
Swift 3
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize{
return CGSize(width: self.myCollectionView.bounds.width, height: self.mylabel.bounds.height)
}
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
// the text that you want to add it to the headerView's label:
fileprivate let myText = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "customHeader",
for: indexPath) as! CustomCollectionReusableView
headerView.lblTitle.text = myText
headerView.backgroundColor = UIColor.lightGray
return headerView
default:
fatalError("This should never happen!!")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
cell.backgroundColor = UIColor.brown
// ...
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
// ofSize should be the same size of the headerView's label size:
return CGSize(width: collectionView.frame.size.width, height: myText.heightWithConstrainedWidth(font: UIFont.systemFont(ofSize: 17)))
}
}
extension String {
func heightWithConstrainedWidth(font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: UIScreen.main.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox.height
}
}
UICollectionReusableView
:class CustomCollectionReusableView: UICollectionReusableView {
@IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// setup "lblTitle":
lblTitle.numberOfLines = 0
lblTitle.lineBreakMode = .byWordWrapping
lblTitle.sizeToFit()
}
}
我使用@Pim的答案解决了问题,但正如评论中的@linus_hologram所提到的,这种解决方案会使AutoLayout抱怨无法满足的约束条件。 我发现一个简单的解决方法:
在 collectionView(_:layout:referenceSizeForHeaderInSection:)
中,不要使用 collectionView(_:viewForSupplementaryElementOfKind:at:)
获取你的 UICollectionView
实例想要重用的视图的引用,而是即时创建你的header视图类的一个实例,并添加一个宽度约束,这样AutoLayout
就能计算出头部的宽度:
let headerView = YourCustomHeaderClass()
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.widthAnchor.constraint(equalToConstant: collectionView.frame.width).isActive = true
return headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
lazy var collectionViewLayout: UICollectionViewLayout = {
let layout = UICollectionViewCompositionalLayout { [weak self] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
// define items, group and section
// (I'll skip some code since it's not relevant to the question)
let itemSize = ...
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(...)
let group = NSCollectionLayoutGroup.horizontal(...)
let section = NSCollectionLayoutSection(group: group)
// define header
// * the magic is here: use .estimated() to dynamically set height for header
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(72))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .topLeading)
section.boundarySupplementaryItems = [header]
return section
}
return layout
}()
viewDidLoad
中设置布局。override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = collectionViewLayout
}