简体   繁体   中英

Calculating Size of Cell for CollectionView Mosaic Layout

I'm trying to make a mosaic collection view layout similar to Google's Keep app. I've subclassed UICollectionViewLayout similar to the many tutorials found online. In order to properly layout the collection view cells, the calling class must implement a delegate, HeightForCellAtIndexPath method to get the cell's height. In my case, I also get the cell's width to create 1, 2 or 3 column layouts.

In all of the tutorials, the height of the cell's content is known and does not need to be computed. In my case, the size of content is not known and needs to be computed. I've tried many different ways of calculating this but none work perfectly. My latest attempt entails creating a CardContent class and adding that to a cell's contentView in cellForItemAt and also instantiate a CardContent instance in HeightForCellAtIndexPath to calculate the size of the content that is passed to the layout class.

I'm sure there are many problems with my methodology, but from what I can gather, the issue appears to be with the multi-line labels not laid out correctly in HeightForCellAtIndexPath in that the labels are not wrapping to multi line and remain as a single line thus giving me an incorrect height of the contentView .

CardContentCell.swift

import UIKit

class CardContentCell: UICollectionViewCell {
    var todoList: TodoList! {
        didSet {
            self.backgroundColor = UIColor(todoList.color)
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.layer.cornerRadius = 5.0
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

CardContent.swift

Edit: Added createLineItem method. See answer below.

class CardContent: UIStackView {

    var todoList: TodoList!

    var verticalItemSpacing: CGFloat = 10.0

    var cellWidth: CGFloat!


    init(todoList: TodoList, cellWidth: CGFloat = 0.0) {
        self.todoList = todoList
        self.cellWidth = cellWidth

        super.init(frame: CGRect(x: 0, y: 0, width: cellWidth, height: 0))

        self.axis = .vertical
        self.alignment = .fill
        self.distribution = .fill
        self.contentMode = .scaleToFill

        self.spacing = 10.0

        layoutContent()
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func createTitleLabel(title: String) -> UILabel {
        let label = UILabel()
        label.text = title
        label.font = label.font.withSize(20.0)
        label.numberOfLines = 2
        label.lineBreakMode = .byTruncatingTail
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }

    func createItemLabel(text: String) -> UILabel {
        let label = UILabel()
        label.text = text
        label.font = label.font.withSize(17.0)
        label.numberOfLines = 3
        label.lineBreakMode = .byTruncatingTail
        label.translatesAutoresizingMaskIntoConstraints = false
        label.sizeToFit()
        return label
    }

    func createLineItem(text: String) -> UIStackView {
        let hstack = UIStackView()
        hstack.axis = .horizontal
        hstack.alignment = .fill
        hstack.distribution = .fillProportionally

        let imgView = createImgView(withFont: lineItemFont)
        let textLabel = createItemLabel(text: text)

        hstack.addArrangedSubview(imgView)
        hstack.addArrangedSubview(textLabel)

        return hstack
    }

    func layoutContent() {

        self.addArrangedSubview(createTitleLabel(title: todoList.title))

        for todo in todoList.todos.prefix(6) {
            let lineItem = createLineItem(text: todo.text)
            self.addArrangedSubview(lineItem)

        }
    }

}

MyCollectionView.swift

extension MyCollectionView: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! CardContentCell
        cell.todoList = todoLists[indexPath.row]
        let content = CardContent(todoList: cell.todoList)
        cell.contentView.addSubview(content)
        content.pinTopAndSides(to: cell.contentView) // See extension below

        return cell
    }
}


extension MyCollectionView: CardLayoutDelegate {
    func collectionView(_ collectionView: UICollectionView, HeightForCellAtIndexPath indexPath: IndexPath, cellWidth: CGFloat) -> CGFloat {

        let todoList = todoLists[indexPath.row]
        let stackView = CardContent(todoList: todoList, cellWidth: cellWidth)

        stackView.translatesAutoresizingMaskIntoConstraints = false

        stackView.setNeedsLayout()
        stackView.layoutIfNeeded()

        let size = stackView.frame.size

        return size.height
    }
}

extension UIView {

    func pinTopAndSides(to other: UIView) {
        translatesAutoresizingMaskIntoConstraints = false
        leadingAnchor.constraint(equalTo: other.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: other.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: other.topAnchor).isActive = true
    }
}

The result is, if there are always 6 line items, then the computed height is always 230 (in a 2 column layout). In the screen shot below, the cell is colored while the rest of the content overflows.

集合视图的屏幕截图

Barring a better solution, the answer for me involved not using a nested horizontal UIStackview . That was fraught with unknowns and hard to diagnose auto layout issues. Instead, I used a UIView and added my own constraints.

Here's the method that creates said view. It's interesting that no one took a close enough look at my question that in my hurry to copy and past, I omitted this most crucial method in the original post. I will update the question with the original implementation of this method for reference.

func createLineItem(text: String) -> UIView {
    let view = UIView()

    let imgView = createImgView(withFont: lineItemFont)
    imgView.translatesAutoresizingMaskIntoConstraints = false

    let textLabel = createItemLabel(text: text)
    textLabel.translatesAutoresizingMaskIntoConstraints = false

    imgView.tintColor = self.textColor

    view.addSubview(imgView)
    view.addSubview(textLabel)

    imgView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    imgView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true

    textLabel.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 5.0).isActive = true
    textLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    textLabel.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    textLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

    return view
}

And,as for the HeightForCellAtIndexPath delegate function, setting the widthAnchor to the cell width provided the correct height of the cell:

func collectionView(_ collectionView: UICollectionView, HeightForCellAtIndexPath indexPath: IndexPath, cellWidth: CGFloat) -> CGFloat {
    let stackView = CardContent(todoList: todoList)
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.widthAnchor.constraint(equalToConstant: cellWidth).isActive = true
    stackView.setNeedsLayout()
    stackView.layoutIfNeeded()
    let size = stackView.frame.size
    return size.height
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM