基于UILabel的动态UICollectionView标头大小

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于UILabel的动态UICollectionView标头大小相关的知识,希望对你有一定的参考价值。

我已经阅读了一些关于向UICollectionView添加标题的帖子。在Swift的ios 7+应用程序中,我正在尝试添加一个带有UILabel的标头,其高度应根据UILabel的高度进行调整。 UILabel的行= 0。

我已经使用AutoLayout在IB中设置了标题

ViewController实现了UICollectionViewDelegate, UICollectionViewDataSource。我没有为标题设置自定义类,但我正在使用这两个函数:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
      //description is a String variable defined in the class
    let size:CGSize = (description as NSString).boundingRectWithSize(CGSizeMake(CGRectGetWidth(collectionView.bounds) - 20.0, 180.0), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont(name: "Helvetica Neue", size: 16.0)], context: nil).size
    return CGSizeMake(CGRectGetWidth(collectionView.bounds), ceil(size.height))
}

func collectionView(collectionView: UICollectionView!, viewForSupplementaryElementOfKind kind: String!, atIndexPath indexPath: NSIndexPath!) -> UICollectionReusableView! {
    var reusableview:UICollectionReusableView = UICollectionReusableView()
    if (kind == UICollectionElementKindSectionHeader) {
                    //listCollectionView is an @IBOutlet UICollectionView defined at class level, using collectionView crashes
            reusableview = listCollectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "ListHeader", forIndexPath: indexPath) as UICollectionReusableView
            let label = reusableview.viewWithTag(200) as UILabel  //the UILabel within the header is tagged with 200
            label.text = description   //description is a String variable defined in the class
        }
    }
    return reusableview
}

显示文本似乎有效,但高度计算似乎不起作用(见下面的截图)。另外,我不认为我可以通过collectionView...referenceSizeForHeaderInSection函数访问UILabel。有关如何正确计算CGSize的任何建议?

答案

这就是我做的方式:

let labels = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac lorem enim. Curabitur rhoncus efficitur quam, et pretium ipsum. Nam eu magna at velit sollicitudin fringilla nec nec nisi. Quisque nec enim et ipsum feugiat pretium. Vestibulum hendrerit arcu ut ipsum gravida, ut tincidunt justo pellentesque. Etiam lacus ligula, aliquet at lorem vel, ullamcorper commodo turpis. Nullam commodo sollicitudin mauris eu faucibus.",
"Lorem ipsum dolor",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac lorem enim. Curabitur rhoncus efficitur quam, et pretium ipsum. Nam eu magna at velit sollicitudin fringilla nec nec nisi. Quisque nec enim et ipsum feugiat pretium."]

基本思想是创建一个相同的UILabel到将在节标题中显示的那个。该标签将用于在referenceSizeForHeaderInSection方法中设置标题的所需大小。

我在我的label子类(UICollectionReusableView)中有一个名为MyHeaderCollectionReusableView的标签出口,我通过在故事板中分配它来将其用于我的节标题视图(将“MyHeader”设置为剖面视图的重用标识符)。提到的标签对部分标题边框具有水平和垂直空间限制,以便正确地自动布局。

override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 3
    }

override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {

        let headerView =
        collectionView.dequeueReusableSupplementaryViewOfKind(kind,
            withReuseIdentifier: "MyHeader",
            forIndexPath: indexPath)
            as MyHeaderCollectionReusableView

        headerView.label.text = labels[indexPath.section]

        return headerView

    }

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        // that -16 is because I have 8px for left and right spacing constraints for the label.
        let label:UILabel = UILabel(frame: CGRectMake(0, 0, collectionView.frame.width - 16, CGFloat.max))
        label.numberOfLines = 0
        label.lineBreakMode = NSLineBreakMode.ByWordWrapping
       //here, be sure you set the font type and size that matches the one set in the storyboard label
        label.font = UIFont(name: "Helvetica", size: 17.0)
        label.text = labels[section]
        label.sizeToFit()

// Set some extra pixels for height due to the margins of the header section.  
//This value should be the sum of the vertical spacing you set in the autolayout constraints for the label. + 16 worked for me as I have 8px for top and bottom constraints.
        return CGSize(width: collectionView.frame.width, height: label.frame.height + 16)
    }
另一答案

就像提问者一样,我有一个UICollectionView,其中包含一个带有单个标签的标题,我希望它的高度可以变化。我创建了UILabel的扩展,以测量具有已知宽度的多线标签的高度:

public extension UILabel {
    public class func size(withText text: String, forWidth width: CGFloat) -> CGSize {
        let measurementLabel = UILabel()
        measurementLabel.text = text
        measurementLabel.numberOfLines = 0
        measurementLabel.lineBreakMode = .byWordWrapping
        measurementLabel.translatesAutoresizingMaskIntoConstraints = false
        measurementLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
        let size = measurementLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
        return size
    }
}

注意:以上是Swift 3语法。

然后我实现了UICollectionViewDelegateFlowLayout的头大小方法:

extension MyCollectionViewController : UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        let text = textForHeader(inSection: section)
        var size =  UILabel.size(withAttributedText: text, forWidth: collectionView.frame.size.width)
        size.height = size.height + 16
        return size
    }
}

计算标题大小的工作委托给上面的UILabel扩展。 +16是一个实验得出的固定偏移量(8 + 8),它基于边距,可以通过编程方式获得。

标头回调中所需的只是设置文本:

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    if kind == UICollectionElementKindSectionHeader, let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as?  MyCollectionHeader {
        let text = textForHeader(inSection: section)
        headerView.label.text = text
        return headerView
    }
    return UICollectionReusableView()
}
另一答案

我们的想法是在内存中有一个模板头实例,以便在创建结果头视图之前计算所需的高度。您应该将节标题视图移动到单独的.nib文件,设置所有自动布局约束并在viewDidLoad方法中实例化模板,如下所示:

class MyViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

  @IBOutlet var collectionView : UICollectionView?
  private var _templateHeader : MyHeaderView

  override func viewDidLoad() {
    super.viewDidLoad()

    let nib = UINib(nibName: "HeaderView", bundle:nil)
    self.collectionView?.registerNib(nib, forCellWithReuseIdentifier: "header_view_id")

    _templateHeader = nib.instantiateWithOwner(nil, options:nil)[0] as! MyHeaderView
  }

}

然后,您将能够在流布局委托方法中计算标题大小(在我的示例中为高度):

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

    _templateHeader.lblTitle.text = "some title here"
    _templateHeader.lblDescription.text = "some long description"

    _templateHeader.setNeedsUpdateConstraints();
    _templateHeader.updateConstraintsIfNeeded()

    _templateHeader.setNeedsLayout();
    _templateHeader.layoutIfNeeded();

    let computedSize = _templateHeader.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

    return CGSizeMake(collectionView.bounds.size.width, computedSize.height);
}

然后一如既往地创建并返回常规标题视图,因为您已经在流布局委托方法中计算了它的大小:

func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {

    switch kind {

    case UICollectionElementKindSectionHeader:

        let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "header_view_id", forIndexPath: indexPath) as! MyHeaderView
        headerView.lblTitle.text = "some title here"
        headerView.lblDescription.text = "some long description"

        headerView.setNeedsUpdateConstraints()
        headerView.updateConstraintsIfNeeded()

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

        return headerView
    default:
        assert(false, "Unexpected kind")
    }

}

忘了说一个重要的时刻 - 你的标题视图应该在其contentView上有一个自动布局约束,以适应集合视图宽度(加上或减去所需的边距)。

另一答案

我们可以简单地使用现有的标签来计算拟合大小,而不是从多个答案中显示的代码重新创建新标签。

你的UICollectionViewDelegate的代码:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    // We get the actual header view
    let header = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: section)) as! MyHeaderView
    // We ask the label what size it takes (eventually accounting for horizontal margins)
    var size = header.myLabel.sizeThatFits(CGSize(width: collectionView.frame.width - horizontalMargins, height: .greatestFiniteMagnitude))
    // We eventually account for vertical margins
  

以上是关于基于UILabel的动态UICollectionView标头大小的主要内容,如果未能解决你的问题,请参考以下文章

具有动态 UILabel 和内容压缩/阻力的 UITableViewCell 高度不正确

基于多行 UILabel 自动调整 UIView 的大小

动态更新 UILabel

在 UITableViewCell 中动态调整 UILabel?

具有动态高度的 UITableViewCell 中的 UILabel

创建单一大小的动态 UILabel 字体