计算 CollectionView Mosaic 布局的单元格大小
Calculating Size of Cell for CollectionView Mosaic Layout
我正在尝试制作类似于 Google 的 Keep 应用程序的马赛克集合视图布局。我已经 subclassed UICollectionViewLayout
类似于网上找到的许多教程。为了正确布局集合视图单元格,调用 class 必须实现一个委托,HeightForCellAtIndexPath
方法来获取单元格的高度。在我的例子中,我还获取了单元格的宽度以创建 1、2 或 3 列布局。
在所有教程中,单元格内容的高度都是已知的,不需要计算。在我的例子中,内容的大小是未知的,需要计算。我尝试了很多不同的计算方法,但 none 效果很好。我最近的尝试需要创建一个 CardContent
class 并将其添加到 cellForItemAt
中单元格的 contentView
并在 HeightForCellAtIndexPath
中实例化一个 CardContent
实例以计算传递给布局的内容的大小 class.
我确定我的方法有很多问题,但据我所知,问题似乎出在 HeightForCellAtIndexPath
中的多行标签布局不正确,因为标签是不换行到多行并保持为单行,因此给我 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
编辑: 添加了 createLineItem
方法。请参阅下面的答案。
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
}
}
结果是,如果始终有 6 个订单项,则计算出的高度始终为 230(在 2 列布局中)。在下面的屏幕截图中,单元格是彩色的,而其余内容溢出了。
除非有更好的解决方案,否则我的答案是不使用嵌套水平 UIStackview
。那充满了未知数,并且很难诊断自动布局问题。相反,我使用了 UIView
并添加了我自己的约束。
这是创建所述视图的方法。有趣的是,没有人仔细看我的问题,以至于我匆忙复制过去,我在原文中省略了这个最关键的方法post。我会用这个方法的原始实现更新问题以供参考。
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
}
并且,对于 HeightForCellAtIndexPath
委托函数,将 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
}
我正在尝试制作类似于 Google 的 Keep 应用程序的马赛克集合视图布局。我已经 subclassed UICollectionViewLayout
类似于网上找到的许多教程。为了正确布局集合视图单元格,调用 class 必须实现一个委托,HeightForCellAtIndexPath
方法来获取单元格的高度。在我的例子中,我还获取了单元格的宽度以创建 1、2 或 3 列布局。
在所有教程中,单元格内容的高度都是已知的,不需要计算。在我的例子中,内容的大小是未知的,需要计算。我尝试了很多不同的计算方法,但 none 效果很好。我最近的尝试需要创建一个 CardContent
class 并将其添加到 cellForItemAt
中单元格的 contentView
并在 HeightForCellAtIndexPath
中实例化一个 CardContent
实例以计算传递给布局的内容的大小 class.
我确定我的方法有很多问题,但据我所知,问题似乎出在 HeightForCellAtIndexPath
中的多行标签布局不正确,因为标签是不换行到多行并保持为单行,因此给我 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
编辑: 添加了 createLineItem
方法。请参阅下面的答案。
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
}
}
结果是,如果始终有 6 个订单项,则计算出的高度始终为 230(在 2 列布局中)。在下面的屏幕截图中,单元格是彩色的,而其余内容溢出了。
除非有更好的解决方案,否则我的答案是不使用嵌套水平 UIStackview
。那充满了未知数,并且很难诊断自动布局问题。相反,我使用了 UIView
并添加了我自己的约束。
这是创建所述视图的方法。有趣的是,没有人仔细看我的问题,以至于我匆忙复制过去,我在原文中省略了这个最关键的方法post。我会用这个方法的原始实现更新问题以供参考。
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
}
并且,对于 HeightForCellAtIndexPath
委托函数,将 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
}