0

I have a quiz in which each question may or may not have an image, and an unknown length of question text.

I am using a Collection View in which the question is in the header, and answers are in the cell. I want the header height to be dynamic to account for the image, and the variable text length. I am close, but there are still a few hiccups.


While browsing SO, and the internet, I have found 2 ways to calculate height of text for a Label and TextView. TextView calculation usually returns a higher height:

Height Calculation

func getHeightForLabelWith(width:CGFloat, font:UIFont, text:String) -> CGFloat {
    let label:UILabel = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
    label.numberOfLines = 0
    label.lineBreakMode = NSLineBreakMode.byWordWrapping
    label.font = font
    label.text = text
    label.sizeToFit()
    return label.frame.height
}

func getHeightForUItextViewWith(width: CGFloat, font:UIFont, text: String) -> CGFloat {
    let textView: UITextView = UITextView.init(frame: CGRect.init(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
    textView.isScrollEnabled = false
    textView.font = font
    textView.text = text
    textView.sizeToFit()
    return textView.frame.height
}

ViewController:

override func viewDidLoad() {
    super.viewDidLoad()

    self.collectionView.dataSource = self
    self.collectionView.delegate = self

    let flowLayout = UICollectionViewFlowLayout()
    flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    flowLayout.minimumLineSpacing = 10
    flowLayout.minimumInteritemSpacing = 10

    collectionView.collectionViewLayout = flowLayout
}

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderCell", for: indexPath) as! HeaderCell

    header.text = headerText
    header.image = headerImage

    return header
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    let height = getHeightForUItextViewWith(width: self.collectionView.bounds.width, font: UIFont.boldSystemFont(ofSize: 20), text: headerText)
    return CGSize(width: self.collectionView.bounds.width, height: max(height, 80))
}

HeaderCell:

class HeaderCell: UICollectionReusableView {
    var image: UIImage?
    var text: String?

    var textView: UITextView = {
        var view = UITextView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.isScrollEnabled = false
        view.font = UIFont.boldSystemFont(ofSize: 20.0)
        return view
    }()

    var imageView: UIImageView = {
        var view = UIImageView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.cellInit()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        self.cellInit()
    }

    func cellInit() {
        self.addSubview(textView)
        self.addSubview(imageView)

        textView.backgroundColor = .orange
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        if let text = text {
            textView.text = text

            textView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
            textView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
            textView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
            textView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true

            textView.sizeToFit()
        }

        if let _ = text, let image = image {
            imageView.image = image

            imageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
            imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
            imageView.heightAnchor.constraint(lessThanOrEqualToConstant: 100.0).isActive = true
            imageView.widthAnchor.constraint(lessThanOrEqualToConstant: 100.0).isActive = true

            let imageFrame = UIBezierPath(rect: imageView.frame)
            textView.textContainer.exclusionPaths = [imageFrame]

            textView.sizeToFit()
        }
    }
}

Snaps:

  1. Small Text, No Image: Cutoff and almost full width
  2. Small Text, Image: Cutoff and almost half width

enter image description here enter image description here 3. Large Text, No Image: Extra height, full width 4. Large Text, Image: Cutoff, full width, extra space under image (not sure if it can be reduced)

enter image description here enter image description here

I hope I have provided enough information for someone to offer feedback.

Edit: Sample project @ G Drive

0 Answers0