使用自动布局的UICollectionView标题动态高度

时间:2016-10-03 05:53:20

标签: ios swift uicollectionview autolayout

我的UICollectionView标题为UICollectionReusableView类型 在其中,我有一个标签,其长度因用户输入而异 因此,我需要根据标签的高度和标题中的其他元素动态调整大小。

这是我的故事板:

storyboard

这是我运行应用程序时的结果:

result

5 个答案:

答案 0 :(得分:20)

这让我绝对疯狂了大约半天。 这是最终奏效的。

确保标题中的标签设置为动态调整大小,一行并换行

  1. 将标签嵌入视图中。这将有助于自动调整大小。 enter image description here

  2. 确保标签上的约束是有限的。例如:从底部标签到可重用视图的大于约束将不起作用。见上图。

  3. 为您在

    中嵌入标签的视图添加插座
    class CustomHeader: UICollectionReusableView {
        @IBOutlet weak var contentView: UIView!
    }
    
  4. 使初始布局无效

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
    
        collectionView.collectionViewLayout.invalidateLayout()
    }
    
  5. 布置标题以获得正确的尺寸

    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)
        }
    
    }
    

答案 1 :(得分:12)

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)
}

https://developer.apple.com/reference/uikit/uicollectionviewdelegateflowlayout/1617702-collectionview

答案 2 :(得分:11)

这是一种优雅,最新的解决方案。

如其他人所述,首先确保所有约束都从标题视图的顶部到第一个子视图的顶部,从第一个子视图的底部到第二个子视图的顶部,依此类推,从最后一个子视图的底部到标题视图的底部。只有这样,自动布局才能知道如何调整视图的大小。

以下代码片段将返回计算出的标题视图的大小。

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
}

答案 3 :(得分:6)

您可以通过实施以下内容来实现:

ViewController:

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() 
    }
}

答案 4 :(得分:-1)

@Pim的答案对我有用,但是正如注释部分中的@linus_hologram所提到的,此解决方案使AutoLayout抱怨约束无法满足。我找到了一个简单的解决方法:

collectionView(_:layout:referenceSizeForHeaderInSection:)中,无需获取对UICollectionView实例要使用collectionView(_:viewForSupplementaryElementOfKind:at:)重用的视图的引用,只需动态创建标题视图类的实例并添加一个宽度限制,以便AutoLayout能够计算标题的宽度:

let headerView = YourCustomHeaderClass() 
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.widthAnchor.constraint(equalToConstant: collectionView.frame.width).isActive = true
let size headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
                    return size
return size