简体   繁体   English

计算 CollectionView 马赛克布局的单元格大小

[英]Calculating Size of Cell for CollectionView Mosaic Layout

I'm trying to make a mosaic collection view layout similar to Google's Keep app.我正在尝试制作类似于 Google 的 Keep 应用的马赛克集合视图布局。 I've subclassed UICollectionViewLayout similar to the many tutorials found online.我对UICollectionViewLayout进行了子类化,类似于在网上找到的许多教程。 In order to properly layout the collection view cells, the calling class must implement a delegate, HeightForCellAtIndexPath method to get the cell's height.为了正确布局集合视图单元格,调用 class 必须实现委托, HeightForCellAtIndexPath方法来获取单元格的高度。 In my case, I also get the cell's width to create 1, 2 or 3 column layouts.就我而言,我还获得了单元格的宽度来创建 1、2 或 3 列布局。

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.我最近的尝试需要创建一个CardContent class 并将其添加到 cellForItemAt 中单元格的contentView中,并在cellForItemAt中实例化一个CardContent实例以计算传递给布局HeightForCellAtIndexPath的内容的大小。

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 .我确定我的方法存在很多问题,但据我所知,问题似乎在于多行标签在HeightForCellAtIndexPath中的布局不正确,因为标签没有换成多行并保持为单行因此给了我不正确的contentView高度。

CardContentCell.swift 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卡片内容.swift

Edit: Added createLineItem method.编辑:添加createLineItem方法。 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 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).结果是,如果始终有 6 个行项目,则计算的高度始终为 230(在 2 列布局中)。 In the screen shot below, the cell is colored while the rest of the content overflows.在下面的屏幕截图中,单元格被着色,而内容的 rest 溢出。

集合视图的屏幕截图

Barring a better solution, the answer for me involved not using a nested horizontal UIStackview .除非有更好的解决方案,否则我的答案是不使用嵌套的水平UIStackview That was fraught with unknowns and hard to diagnose auto layout issues.这充满了未知数并且难以诊断自动布局问题。 Instead, I used a UIView and added my own constraints.相反,我使用了UIView并添加了我自己的约束。

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:而且,对于HeightForCellAtIndexPath委托 function,将widthAnchor设置为单元格宽度提供了正确的单元格高度:

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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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